openproject-primer_view_components 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/app/assets/javascripts/components/primer/open_project/border_box/collapsible_header.d.ts +18 -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/assets/styles/primer_view_components.css +1 -1
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/select_panel.html.erb +5 -0
- data/app/components/primer/alpha/select_panel.rb +23 -24
- data/app/components/primer/alpha/select_panel_element.js +6 -4
- data/app/components/primer/alpha/select_panel_element.ts +8 -4
- data/app/components/primer/open_project/border_box/collapsible_header.css +1 -0
- data/app/components/primer/open_project/border_box/collapsible_header.css.json +10 -0
- data/app/components/primer/open_project/border_box/collapsible_header.css.map +1 -0
- data/app/components/primer/open_project/border_box/collapsible_header.d.ts +18 -0
- data/app/components/primer/open_project/border_box/collapsible_header.html.erb +17 -0
- data/app/components/primer/open_project/border_box/collapsible_header.js +70 -0
- data/app/components/primer/open_project/border_box/collapsible_header.pcss +19 -0
- data/app/components/primer/open_project/border_box/collapsible_header.rb +53 -0
- data/app/components/primer/open_project/border_box/collapsible_header.ts +72 -0
- data/app/components/primer/primer.d.ts +1 -0
- data/app/components/primer/primer.js +1 -0
- data/app/components/primer/primer.pcss +1 -0
- data/app/components/primer/primer.ts +1 -0
- data/lib/primer/view_components/version.rb +1 -1
- data/previews/primer/alpha/select_panel_preview/remote_fetch_form.html.erb +34 -0
- data/previews/primer/alpha/select_panel_preview.rb +9 -0
- data/previews/primer/open_project/border_box/collapsible_header_preview/playground.html.erb +14 -0
- data/previews/primer/open_project/border_box/collapsible_header_preview.rb +88 -0
- data/static/arguments.json +18 -80
- data/static/audited_at.json +1 -0
- data/static/classes.json +5 -1
- data/static/constants.json +3 -0
- data/static/info_arch.json +114 -81
- data/static/previews.json +86 -0
- data/static/statuses.json +1 -0
- metadata +17 -4
@@ -1,4 +1,9 @@
|
|
1
1
|
<%= render Primer::BaseComponent.new(**@system_arguments) do %>
|
2
|
+
<% if required_form_arguments_given? %>
|
3
|
+
<span data-select-panel-inputs="true">
|
4
|
+
<%= @form_builder.hidden_field(@input_name, multiple: multi_select?, skip_default_ids: true, value: @value) %>
|
5
|
+
</span>
|
6
|
+
<% end %>
|
2
7
|
<dialog-helper>
|
3
8
|
<%= show_button %>
|
4
9
|
<%= render(@dialog) do %>
|
@@ -152,27 +152,6 @@ module Primer
|
|
152
152
|
# end
|
153
153
|
# end
|
154
154
|
# ```
|
155
|
-
#
|
156
|
-
# If items are provided dynamically, things become a bit more complicated. The `form_for` or `form_with` method call
|
157
|
-
# happens in the view that renders the `SelectPanel`, which means the form builder object but isn't available in the
|
158
|
-
# view that renders the list items. In such a case, it can be useful to create an instance of the form builder maually:
|
159
|
-
#
|
160
|
-
# ```erb
|
161
|
-
# <% builder = ActionView::Helpers::FormBuilder.new(
|
162
|
-
# "address", # the name of the model, used to wrap input names, eg 'address[country]'
|
163
|
-
# nil, # object (eg. the Address instance, which we can omit)
|
164
|
-
# self, # template
|
165
|
-
# {} # options
|
166
|
-
# ) %>
|
167
|
-
# <%= render(Primer::Alpha::SelectPanel::ItemList.new(
|
168
|
-
# form_arguments: { builder: builder, name: "country" }
|
169
|
-
# )) do |list| %>
|
170
|
-
# <% countries.each do |country| %>
|
171
|
-
# <% menu.with_item(label: country.name, content_arguments: { data: { value: country.code } }) %>
|
172
|
-
# <% end %>
|
173
|
-
# <% end %>
|
174
|
-
# ```
|
175
|
-
#
|
176
155
|
# ### JavaScript API
|
177
156
|
#
|
178
157
|
# `SelectPanel`s render a `<select-panel>` custom element that exposes behavior to the client.
|
@@ -375,7 +354,9 @@ module Primer
|
|
375
354
|
# @param dynamic_aria_label_prefix [String] If provided, the prefix is prepended to the dynamic label and set as the value of the `aria-label` attribute on the show button.
|
376
355
|
# @param body_id [String] The unique ID of the panel body. If not provided, the body ID will be set to the panel ID with a "-body" suffix.
|
377
356
|
# @param list_arguments [Hash] Arguments to pass to the underlying <%= link_to_component(Primer::Alpha::ActionList) %> component. Only has an effect for the local fetch strategy.
|
378
|
-
# @param form_arguments [Hash] Form arguments
|
357
|
+
# @param form_arguments [Hash] Form arguments
|
358
|
+
|
359
|
+
# @param use_experimental_non_local_form [Boolean] A feature flag used to slowly roll out moving the input field (generated from form arguments) to the top of the SelectPanel HTML thus allowing remote fetching to have default form values.
|
379
360
|
# @param show_filter [Boolean] Whether or not to show the filter input.
|
380
361
|
# @param open_on_load [Boolean] Open the panel when the page loads.
|
381
362
|
# @param anchor_align [Symbol] The anchor alignment of the Overlay. <%= one_of(Primer::Alpha::Overlay::ANCHOR_ALIGN_OPTIONS) %>
|
@@ -397,6 +378,7 @@ module Primer
|
|
397
378
|
dynamic_label_prefix: nil,
|
398
379
|
dynamic_aria_label_prefix: nil,
|
399
380
|
body_id: nil,
|
381
|
+
use_experimental_non_local_form: false,
|
400
382
|
list_arguments: {},
|
401
383
|
form_arguments: {},
|
402
384
|
show_filter: true,
|
@@ -429,6 +411,15 @@ module Primer
|
|
429
411
|
@dynamic_aria_label_prefix = dynamic_aria_label_prefix
|
430
412
|
@loading_label = loading_label
|
431
413
|
@loading_description_id = nil
|
414
|
+
|
415
|
+
if use_experimental_non_local_form
|
416
|
+
@form_builder = form_arguments[:builder]
|
417
|
+
@value = form_arguments[:value]
|
418
|
+
@input_name = form_arguments[:name]
|
419
|
+
end
|
420
|
+
|
421
|
+
@list_form_arguments = use_experimental_non_local_form ? {} : form_arguments
|
422
|
+
|
432
423
|
if loading_description.present?
|
433
424
|
@loading_description_id = "#{@panel_id}-loading-description"
|
434
425
|
end
|
@@ -471,7 +462,7 @@ module Primer
|
|
471
462
|
|
472
463
|
@list = Primer::Alpha::SelectPanel::ItemList.new(
|
473
464
|
**list_arguments,
|
474
|
-
form_arguments:
|
465
|
+
form_arguments: @list_form_arguments,
|
475
466
|
id: "#{@panel_id}-list",
|
476
467
|
select_variant: @select_variant,
|
477
468
|
aria: {
|
@@ -546,6 +537,14 @@ module Primer
|
|
546
537
|
def before_render
|
547
538
|
content
|
548
539
|
end
|
540
|
+
|
541
|
+
def required_form_arguments_given?
|
542
|
+
@input_name && @form_builder
|
543
|
+
end
|
544
|
+
|
545
|
+
def multi_select?
|
546
|
+
select_variant == :multiple
|
547
|
+
end
|
549
548
|
end
|
550
549
|
end
|
551
|
-
end
|
550
|
+
end
|
@@ -893,7 +893,8 @@ _SelectPanelElement_setDynamicLabel = function _SelectPanelElement_setDynamicLab
|
|
893
893
|
};
|
894
894
|
_SelectPanelElement_updateInput = function _SelectPanelElement_updateInput() {
|
895
895
|
if (this.selectVariant === 'single') {
|
896
|
-
const input = this.querySelector(`[data-
|
896
|
+
const input = this.querySelector(`[data-select-panel-inputs=true] input`) ??
|
897
|
+
this.querySelector(`[data-list-inputs=true] input`);
|
897
898
|
if (!input)
|
898
899
|
return;
|
899
900
|
const selectedItem = this.selectedItems[0];
|
@@ -903,13 +904,14 @@ _SelectPanelElement_updateInput = function _SelectPanelElement_updateInput() {
|
|
903
904
|
input.name = selectedItem.inputName;
|
904
905
|
input.removeAttribute('disabled');
|
905
906
|
}
|
906
|
-
else {
|
907
|
+
else if (__classPrivateFieldGet(this, _SelectPanelElement_hasLoadedData, "f")) {
|
907
908
|
input.setAttribute('disabled', 'disabled');
|
908
909
|
}
|
909
910
|
}
|
910
911
|
else if (this.selectVariant !== 'none') {
|
911
912
|
// multiple select variant
|
912
|
-
const
|
913
|
+
const isRemoteInput = !!this.querySelector('[data-select-panel-inputs=true]');
|
914
|
+
const inputList = this.querySelector('[data-select-panel-inputs=true]') ?? this.querySelector('[data-list-inputs=true]');
|
913
915
|
if (!inputList)
|
914
916
|
return;
|
915
917
|
const inputs = inputList.querySelectorAll('input');
|
@@ -918,7 +920,7 @@ _SelectPanelElement_updateInput = function _SelectPanelElement_updateInput() {
|
|
918
920
|
}
|
919
921
|
for (const selectedItem of this.selectedItems) {
|
920
922
|
const newInput = document.createElement('input');
|
921
|
-
newInput.setAttribute('data-list-input'
|
923
|
+
newInput.setAttribute(`${isRemoteInput ? 'data-select-panel-input' : 'data-list-input'}`, 'true');
|
922
924
|
newInput.type = 'hidden';
|
923
925
|
newInput.autocomplete = 'off';
|
924
926
|
newInput.name = selectedItem.inputName || __classPrivateFieldGet(this, _SelectPanelElement_inputName, "f");
|
@@ -959,7 +959,9 @@ export class SelectPanelElement extends HTMLElement {
|
|
959
959
|
|
960
960
|
#updateInput() {
|
961
961
|
if (this.selectVariant === 'single') {
|
962
|
-
const input =
|
962
|
+
const input =
|
963
|
+
(this.querySelector(`[data-select-panel-inputs=true] input`) as HTMLInputElement) ??
|
964
|
+
(this.querySelector(`[data-list-inputs=true] input`) as HTMLInputElement)
|
963
965
|
if (!input) return
|
964
966
|
|
965
967
|
const selectedItem = this.selectedItems[0]
|
@@ -968,12 +970,14 @@ export class SelectPanelElement extends HTMLElement {
|
|
968
970
|
input.value = (selectedItem.value || selectedItem.label || '').trim()
|
969
971
|
if (selectedItem.inputName) input.name = selectedItem.inputName
|
970
972
|
input.removeAttribute('disabled')
|
971
|
-
} else {
|
973
|
+
} else if (this.#hasLoadedData) {
|
972
974
|
input.setAttribute('disabled', 'disabled')
|
973
975
|
}
|
974
976
|
} else if (this.selectVariant !== 'none') {
|
975
977
|
// multiple select variant
|
976
|
-
const
|
978
|
+
const isRemoteInput = !!this.querySelector('[data-select-panel-inputs=true]')
|
979
|
+
const inputList =
|
980
|
+
this.querySelector('[data-select-panel-inputs=true]') ?? this.querySelector('[data-list-inputs=true]')
|
977
981
|
if (!inputList) return
|
978
982
|
|
979
983
|
const inputs = inputList.querySelectorAll('input')
|
@@ -984,7 +988,7 @@ export class SelectPanelElement extends HTMLElement {
|
|
984
988
|
|
985
989
|
for (const selectedItem of this.selectedItems) {
|
986
990
|
const newInput = document.createElement('input')
|
987
|
-
newInput.setAttribute('data-list-input'
|
991
|
+
newInput.setAttribute(`${isRemoteInput ? 'data-select-panel-input' : 'data-list-input'}`, 'true')
|
988
992
|
newInput.type = 'hidden'
|
989
993
|
newInput.autocomplete = 'off'
|
990
994
|
newInput.name = selectedItem.inputName || this.#inputName
|
@@ -0,0 +1 @@
|
|
1
|
+
.CollapsibleHeader{align-items:center;cursor:pointer;display:flex}.Box:has(.CollapsibleHeader--collapsed){border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom-width:var(--borderWidth-thicker)}.Box:has(.CollapsibleHeader--collapsed) .Box-body,.Box:has(.CollapsibleHeader--collapsed) .Box-footer,.Box:has(.CollapsibleHeader--collapsed) .Box-row{display:none}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
{
|
2
|
+
"name": "open_project/border_box/collapsible_header",
|
3
|
+
"selectors": [
|
4
|
+
".CollapsibleHeader",
|
5
|
+
".Box:has(.CollapsibleHeader--collapsed)",
|
6
|
+
".Box:has(.CollapsibleHeader--collapsed) .Box-body",
|
7
|
+
".Box:has(.CollapsibleHeader--collapsed) .Box-footer",
|
8
|
+
".Box:has(.CollapsibleHeader--collapsed) .Box-row"
|
9
|
+
]
|
10
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["collapsible_header.pcss"],"names":[],"mappings":"AAEA,mBAGI,kBAAmB,CAFnB,cAAe,CACf,YAEJ,CAEA,wCACI,2BAA4B,CAC5B,4BAA6B,CAC7B,8CAOJ,CALI,uJAGI,YACJ","file":"collapsible_header.css","sourcesContent":["/* CSS for BorderBox::CollapsibleHeader */\n\n.CollapsibleHeader {\n cursor: pointer;\n display: flex;\n align-items: center;\n}\n\n.Box:has(.CollapsibleHeader--collapsed) {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-width: var(--borderWidth-thicker);\n\n & .Box-row,\n & .Box-body,\n & .Box-footer {\n display: none;\n }\n}\n"]}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
declare class CollapsibleHeaderElement extends HTMLElement {
|
2
|
+
arrowDown: HTMLElement;
|
3
|
+
arrowUp: HTMLElement;
|
4
|
+
description: HTMLElement;
|
5
|
+
collapsed: string;
|
6
|
+
private _collapsed;
|
7
|
+
constructor();
|
8
|
+
connectedCallback(): void;
|
9
|
+
toggle(): void;
|
10
|
+
private hideAll;
|
11
|
+
private expandAll;
|
12
|
+
}
|
13
|
+
declare global {
|
14
|
+
interface Window {
|
15
|
+
CollapsibleHeaderElement: typeof CollapsibleHeaderElement;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
export {};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
|
2
|
+
<%= render(Primer::OpenProject::FlexLayout.new) do |flex| %>
|
3
|
+
<%= flex.with_row do %>
|
4
|
+
<%= render(Primer::Beta::Text.new(mr: 2) ) { @title } %>
|
5
|
+
<% if @count.present? %>
|
6
|
+
<%= render(Primer::Beta::Counter.new(count: @count, mr: 2, scheme: :primary)) %>
|
7
|
+
<% end %>
|
8
|
+
<%= render(Primer::Beta::Octicon.new(icon: "chevron-up", data: { target: "collapsible-header.arrowUp" })) %>
|
9
|
+
<%= render(Primer::Beta::Octicon.new(icon: "chevron-down", display: :none, data: { target: "collapsible-header.arrowDown" })) %>
|
10
|
+
<% end %>
|
11
|
+
<%= flex.with_row do %>
|
12
|
+
<% if @description.present? %>
|
13
|
+
<%= render(Primer::Beta::Text.new(color: :subtle, data: { target: "collapsible-header.description" }) ) { @description } %>
|
14
|
+
<% end %>
|
15
|
+
<% end %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
@@ -0,0 +1,70 @@
|
|
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 { attr, controller, target } from '@github/catalyst';
|
8
|
+
let CollapsibleHeaderElement = class CollapsibleHeaderElement extends HTMLElement {
|
9
|
+
// eslint-disable-next-line custom-elements/no-constructor
|
10
|
+
constructor() {
|
11
|
+
super();
|
12
|
+
if (!this.closest('.Box')) {
|
13
|
+
throw new Error('No surrounding BorderBox found');
|
14
|
+
}
|
15
|
+
}
|
16
|
+
connectedCallback() {
|
17
|
+
if (this.collapsed === 'true') {
|
18
|
+
this._collapsed = true;
|
19
|
+
this.hideAll();
|
20
|
+
}
|
21
|
+
else {
|
22
|
+
this._collapsed = false;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
toggle() {
|
26
|
+
if (this._collapsed) {
|
27
|
+
this._collapsed = false;
|
28
|
+
this.expandAll();
|
29
|
+
}
|
30
|
+
else {
|
31
|
+
this._collapsed = true;
|
32
|
+
this.hideAll();
|
33
|
+
}
|
34
|
+
}
|
35
|
+
hideAll() {
|
36
|
+
this.arrowDown.classList.remove('d-none');
|
37
|
+
this.arrowUp.classList.add('d-none');
|
38
|
+
if (this.description !== undefined) {
|
39
|
+
this.description.classList.add('d-none');
|
40
|
+
}
|
41
|
+
this.classList.add('CollapsibleHeader--collapsed');
|
42
|
+
}
|
43
|
+
expandAll() {
|
44
|
+
this.arrowDown.classList.add('d-none');
|
45
|
+
this.arrowUp.classList.remove('d-none');
|
46
|
+
if (this.description !== undefined) {
|
47
|
+
this.description.classList.remove('d-none');
|
48
|
+
}
|
49
|
+
this.classList.remove('CollapsibleHeader--collapsed');
|
50
|
+
}
|
51
|
+
};
|
52
|
+
__decorate([
|
53
|
+
target
|
54
|
+
], CollapsibleHeaderElement.prototype, "arrowDown", void 0);
|
55
|
+
__decorate([
|
56
|
+
target
|
57
|
+
], CollapsibleHeaderElement.prototype, "arrowUp", void 0);
|
58
|
+
__decorate([
|
59
|
+
target
|
60
|
+
], CollapsibleHeaderElement.prototype, "description", void 0);
|
61
|
+
__decorate([
|
62
|
+
attr
|
63
|
+
], CollapsibleHeaderElement.prototype, "collapsed", void 0);
|
64
|
+
CollapsibleHeaderElement = __decorate([
|
65
|
+
controller
|
66
|
+
], CollapsibleHeaderElement);
|
67
|
+
if (!window.customElements.get('collapsible-header')) {
|
68
|
+
window.CollapsibleHeaderElement = CollapsibleHeaderElement;
|
69
|
+
window.customElements.define('collapsible-header', CollapsibleHeaderElement);
|
70
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
/* CSS for BorderBox::CollapsibleHeader */
|
2
|
+
|
3
|
+
.CollapsibleHeader {
|
4
|
+
cursor: pointer;
|
5
|
+
display: flex;
|
6
|
+
align-items: center;
|
7
|
+
}
|
8
|
+
|
9
|
+
.Box:has(.CollapsibleHeader--collapsed) {
|
10
|
+
border-bottom-left-radius: 0;
|
11
|
+
border-bottom-right-radius: 0;
|
12
|
+
border-bottom-width: var(--borderWidth-thicker);
|
13
|
+
|
14
|
+
& .Box-row,
|
15
|
+
& .Box-body,
|
16
|
+
& .Box-footer {
|
17
|
+
display: none;
|
18
|
+
}
|
19
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module OpenProject
|
5
|
+
module BorderBox
|
6
|
+
# Add a general description of component here
|
7
|
+
# Add additional usage considerations or best practices that may aid the user to use the component correctly.
|
8
|
+
# @accessibility Add any accessibility considerations
|
9
|
+
class CollapsibleHeader < Primer::Component
|
10
|
+
status :open_project
|
11
|
+
|
12
|
+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
13
|
+
def initialize(title:, count: nil, description: nil, collapsed: false, box:, **system_arguments)
|
14
|
+
@title = title
|
15
|
+
@count = count
|
16
|
+
@description = description
|
17
|
+
@collapsed = collapsed
|
18
|
+
@box = box
|
19
|
+
@system_arguments = system_arguments
|
20
|
+
|
21
|
+
@system_arguments[:tag] = "collapsible-header"
|
22
|
+
@system_arguments[:classes] = class_names(
|
23
|
+
system_arguments[:classes],
|
24
|
+
"CollapsibleHeader",
|
25
|
+
"CollapsibleHeader--collapsed" => @collapsed
|
26
|
+
)
|
27
|
+
@system_arguments[:data] = merge_data(
|
28
|
+
@system_arguments, {
|
29
|
+
data: {
|
30
|
+
action: "click:collapsible-header#toggle",
|
31
|
+
collapsed: @collapsed
|
32
|
+
} }
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def before_render
|
39
|
+
content
|
40
|
+
end
|
41
|
+
|
42
|
+
def render?
|
43
|
+
raise ArgumentError, "Title must be present" unless @title.present?
|
44
|
+
raise ArgumentError, "Description cannot be a blank string" unless @description.present? || @description.nil?
|
45
|
+
raise ArgumentError, "Count cannot be a blank string" unless @count.present? || @count.nil?
|
46
|
+
raise ArgumentError, "This component must be called inside the header of a `Primer::Beta::BorderBox`" unless @box.present? && @box.is_a?(Primer::Beta::BorderBox)
|
47
|
+
|
48
|
+
true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import {attr, controller, target} from '@github/catalyst'
|
2
|
+
|
3
|
+
@controller
|
4
|
+
class CollapsibleHeaderElement extends HTMLElement {
|
5
|
+
@target arrowDown: HTMLElement
|
6
|
+
@target arrowUp: HTMLElement
|
7
|
+
@target description: HTMLElement
|
8
|
+
|
9
|
+
@attr collapsed: string
|
10
|
+
private _collapsed: boolean
|
11
|
+
|
12
|
+
// eslint-disable-next-line custom-elements/no-constructor
|
13
|
+
constructor() {
|
14
|
+
super()
|
15
|
+
|
16
|
+
if (!this.closest('.Box')) {
|
17
|
+
throw new Error('No surrounding BorderBox found')
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
connectedCallback() {
|
22
|
+
if (this.collapsed === 'true') {
|
23
|
+
this._collapsed = true
|
24
|
+
this.hideAll()
|
25
|
+
} else {
|
26
|
+
this._collapsed = false
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
public toggle() {
|
31
|
+
if (this._collapsed) {
|
32
|
+
this._collapsed = false
|
33
|
+
this.expandAll()
|
34
|
+
} else {
|
35
|
+
this._collapsed = true
|
36
|
+
this.hideAll()
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
private hideAll() {
|
41
|
+
this.arrowDown.classList.remove('d-none')
|
42
|
+
this.arrowUp.classList.add('d-none')
|
43
|
+
|
44
|
+
if (this.description !== undefined) {
|
45
|
+
this.description.classList.add('d-none')
|
46
|
+
}
|
47
|
+
|
48
|
+
this.classList.add('CollapsibleHeader--collapsed')
|
49
|
+
}
|
50
|
+
|
51
|
+
private expandAll() {
|
52
|
+
this.arrowDown.classList.add('d-none')
|
53
|
+
this.arrowUp.classList.remove('d-none')
|
54
|
+
|
55
|
+
if (this.description !== undefined) {
|
56
|
+
this.description.classList.remove('d-none')
|
57
|
+
}
|
58
|
+
|
59
|
+
this.classList.remove('CollapsibleHeader--collapsed')
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
declare global {
|
64
|
+
interface Window {
|
65
|
+
CollapsibleHeaderElement: typeof CollapsibleHeaderElement
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
if (!window.customElements.get('collapsible-header')) {
|
70
|
+
window.CollapsibleHeaderElement = CollapsibleHeaderElement
|
71
|
+
window.customElements.define('collapsible-header', CollapsibleHeaderElement)
|
72
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<% subject_id = SecureRandom.hex %>
|
2
|
+
|
3
|
+
<%= form_with(url: generic_form_submission_path(format: route_format)) do |builder| %>
|
4
|
+
<%= render(Primer::Alpha::SelectPanel.new(
|
5
|
+
data: { interaction_subject: subject_id },
|
6
|
+
select_variant: :single,
|
7
|
+
src: select_panel_items_path(
|
8
|
+
select_variant: :single,
|
9
|
+
selected_items: selected_items,
|
10
|
+
),
|
11
|
+
open_on_load: open_on_load,
|
12
|
+
dynamic_label: true,
|
13
|
+
dynamic_label_prefix: "Item",
|
14
|
+
use_experimental_non_local_form: true,
|
15
|
+
form_arguments: {
|
16
|
+
name: :item,
|
17
|
+
builder: builder,
|
18
|
+
value: 3,
|
19
|
+
}
|
20
|
+
)) do |panel| %>
|
21
|
+
<% panel.with_show_button { "Sci-fi equipment" } %>
|
22
|
+
<% panel.with_footer(show_divider: true) do %>
|
23
|
+
I'm a footer!
|
24
|
+
<% end %>
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
<hr>
|
28
|
+
|
29
|
+
<%= render(Primer::Beta::Button.new(type: :submit, scheme: :primary)) do %>
|
30
|
+
Submit
|
31
|
+
<% end %>
|
32
|
+
<% end %>
|
33
|
+
|
34
|
+
<%= render partial: "primer/alpha/select_panel_preview/interaction_subject_js", locals: { subject_id: subject_id } %>
|
@@ -259,6 +259,15 @@ module Primer
|
|
259
259
|
render_with_template(locals: { open_on_load: open_on_load, route_format: route_format })
|
260
260
|
end
|
261
261
|
|
262
|
+
# @label Remote fetch form
|
263
|
+
#
|
264
|
+
# @snapshot interactive
|
265
|
+
# @param open_on_load toggle
|
266
|
+
# @param selected_items text
|
267
|
+
def remote_fetch_form(open_on_load: false, selected_items: "Phaser", route_format: :html)
|
268
|
+
render_with_template(locals: { open_on_load: open_on_load, selected_items: selected_items, route_format: route_format })
|
269
|
+
end
|
270
|
+
|
262
271
|
# @label Multi-select form
|
263
272
|
#
|
264
273
|
# @snapshot interactive
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<%= render(Primer::Beta::BorderBox.new) do |component| %>
|
2
|
+
<% component.with_header do %>
|
3
|
+
<%= render(Primer::OpenProject::BorderBox::CollapsibleHeader.new(box: component,
|
4
|
+
title: title,
|
5
|
+
description: description,
|
6
|
+
count: count,
|
7
|
+
collapsed: collapsed)) %>
|
8
|
+
<% end %>
|
9
|
+
<% component.with_body { "Body" } %>
|
10
|
+
<% component.with_row { "Row 1" } %>
|
11
|
+
<% component.with_row { "Row 2" } %>
|
12
|
+
<% component.with_row { "Row 3" } %>
|
13
|
+
<% component.with_footer { "Footer" } %>
|
14
|
+
<% end %>
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Setup Playground to use all available component props
|
4
|
+
# Setup Features to use individual component props and combinations
|
5
|
+
|
6
|
+
module Primer
|
7
|
+
module OpenProject
|
8
|
+
module BorderBox
|
9
|
+
# @label CollapsibleHeader
|
10
|
+
class CollapsibleHeaderPreview < ViewComponent::Preview
|
11
|
+
# @label Playground
|
12
|
+
# @param title [String]
|
13
|
+
# @param description [String]
|
14
|
+
# @param collapsed toggle
|
15
|
+
# @param count [Integer]
|
16
|
+
def playground(
|
17
|
+
title: "Backlog",
|
18
|
+
description: "This backlog is unique to this one-time meeting. You can drag items in and out to add or remove them from the meeting agenda.",
|
19
|
+
count: 42,
|
20
|
+
collapsed: false
|
21
|
+
)
|
22
|
+
render_with_template(locals: {
|
23
|
+
title: title,
|
24
|
+
description: description,
|
25
|
+
count: count,
|
26
|
+
collapsed: collapsed
|
27
|
+
})
|
28
|
+
end
|
29
|
+
|
30
|
+
# @label Default
|
31
|
+
# @snapshot
|
32
|
+
def default
|
33
|
+
render_with_template(
|
34
|
+
template: "primer/open_project/border_box/collapsible_header_preview/playground",
|
35
|
+
locals: {
|
36
|
+
title: "Backlog",
|
37
|
+
description: nil,
|
38
|
+
count: nil,
|
39
|
+
collapsed: false
|
40
|
+
}
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @label With counter
|
45
|
+
# @snapshot
|
46
|
+
def with_count
|
47
|
+
render_with_template(
|
48
|
+
template: "primer/open_project/border_box/collapsible_header_preview/playground",
|
49
|
+
locals: {
|
50
|
+
title: "Backlog",
|
51
|
+
description: nil,
|
52
|
+
count: 42,
|
53
|
+
collapsed: false
|
54
|
+
}
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @label With description text
|
59
|
+
# @snapshot
|
60
|
+
def with_description
|
61
|
+
render_with_template(
|
62
|
+
template: "primer/open_project/border_box/collapsible_header_preview/playground",
|
63
|
+
locals: {
|
64
|
+
title: "Backlog",
|
65
|
+
description: "This backlog is unique to this one-time meeting. You can drag items in and out to add or remove them from the meeting agenda.",
|
66
|
+
count: nil,
|
67
|
+
collapsed: false
|
68
|
+
}
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
# @label Collapsed initially
|
73
|
+
# @snapshot
|
74
|
+
def collapsed
|
75
|
+
render_with_template(
|
76
|
+
template: "primer/open_project/border_box/collapsible_header_preview/playground",
|
77
|
+
locals: {
|
78
|
+
title: "Backlog",
|
79
|
+
description: "This backlog is unique to this one-time meeting. You can drag items in and out to add or remove them from the meeting agenda.",
|
80
|
+
count: nil,
|
81
|
+
collapsed: true
|
82
|
+
}
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|