openproject-primer_view_components 0.87.0 → 0.89.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/app/assets/javascripts/primer_view_components.js +1 -1
  4. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  5. data/app/assets/styles/primer_view_components.css +1 -1
  6. data/app/assets/styles/primer_view_components.css.map +1 -1
  7. data/app/components/primer/alpha/tree_view/tree_view.js +27 -11
  8. data/app/components/primer/alpha/tree_view/tree_view.ts +24 -9
  9. data/app/components/primer/alpha/tree_view.css +1 -1
  10. data/app/components/primer/alpha/tree_view.css.json +1 -2
  11. data/app/components/primer/alpha/tree_view.css.map +1 -1
  12. data/app/components/primer/alpha/tree_view.pcss +11 -7
  13. data/app/components/primer/open_project/avatar_fallback.js +1 -1
  14. data/app/components/primer/open_project/avatar_fallback.ts +1 -1
  15. data/app/components/primer/open_project/border_box/collapsible_header.js +1 -1
  16. data/app/components/primer/open_project/border_box/collapsible_header.ts +1 -1
  17. data/app/components/primer/open_project/collapsible_section.js +1 -1
  18. data/app/components/primer/open_project/collapsible_section.ts +1 -1
  19. data/app/components/primer/open_project/danger_dialog_form_helper.js +1 -1
  20. data/app/components/primer/open_project/danger_dialog_form_helper.ts +1 -1
  21. data/app/components/primer/open_project/fieldset.rb +2 -2
  22. data/app/components/primer/open_project/filterable_tree_view.css +1 -1
  23. data/app/components/primer/open_project/filterable_tree_view.css.map +1 -1
  24. data/app/components/primer/open_project/filterable_tree_view.js +12 -5
  25. data/app/components/primer/open_project/filterable_tree_view.pcss +2 -2
  26. data/app/components/primer/open_project/filterable_tree_view.rb +7 -0
  27. data/app/components/primer/open_project/filterable_tree_view.ts +10 -5
  28. data/app/components/primer/open_project/flex_layout.rb +2 -2
  29. data/app/components/primer/open_project/inline_message.rb +1 -1
  30. data/app/components/primer/open_project/input_group.html.erb +9 -0
  31. data/app/components/primer/open_project/input_group.rb +13 -0
  32. data/app/components/primer/open_project/page_header.rb +1 -1
  33. data/app/components/primer/open_project/page_header_element.js +1 -1
  34. data/app/components/primer/open_project/page_header_element.ts +1 -1
  35. data/app/components/primer/open_project/pagination.rb +1 -3
  36. data/app/components/primer/open_project/sub_header/button.rb +1 -1
  37. data/app/components/primer/open_project/sub_header.html.erb +8 -8
  38. data/app/components/primer/open_project/sub_header.rb +19 -18
  39. data/app/components/primer/open_project/sub_header_element.js +1 -1
  40. data/app/components/primer/open_project/sub_header_element.ts +1 -1
  41. data/app/components/primer/open_project/zen_mode_button.js +1 -1
  42. data/app/components/primer/open_project/zen_mode_button.ts +1 -1
  43. data/app/controllers/primer/view_components/filterable_tree_view_items_controller.rb +22 -5
  44. data/config/locales/en.yml +1 -0
  45. data/lib/primer/view_components/version.rb +1 -1
  46. data/previews/primer/alpha/tree_view_preview/doubled_path.html.erb +12 -0
  47. data/previews/primer/alpha/tree_view_preview/form_input.html.erb +7 -7
  48. data/previews/primer/alpha/tree_view_preview.rb +6 -0
  49. data/previews/primer/open_project/filterable_tree_view_preview/async_server_highlights.html.erb +4 -0
  50. data/previews/primer/open_project/filterable_tree_view_preview.rb +12 -0
  51. data/previews/primer/open_project/input_group_preview.rb +9 -0
  52. data/previews/primer/open_project/sub_header_preview/quick_filters.html.erb +2 -2
  53. data/previews/primer/open_project/sub_header_preview.rb +12 -12
  54. data/static/arguments.json +8 -8
  55. data/static/info_arch.json +32 -9
  56. data/static/previews.json +23 -10
  57. metadata +3 -1
@@ -18,7 +18,7 @@ type NodeState = {
18
18
 
19
19
  const ASYNC_DEBOUNCE_MS = 300
20
20
 
21
- @controller
21
+ @controller('filterable-tree-view')
22
22
  export class FilterableTreeViewElement extends HTMLElement {
23
23
  @target filterInput: HTMLInputElement
24
24
  @target filterModeControlList: HTMLElement
@@ -69,6 +69,10 @@ export class FilterableTreeViewElement extends HTMLElement {
69
69
  return !!this.#src
70
70
  }
71
71
 
72
+ get #clientHighlightsEnabled(): boolean {
73
+ return this.getAttribute('data-show-search-highlighting') !== 'false'
74
+ }
75
+
72
76
  handleEvent(event: Event) {
73
77
  if (event.target === this.filterModeControl) {
74
78
  this.#handleFilterModeEvent(event)
@@ -119,7 +123,8 @@ export class FilterableTreeViewElement extends HTMLElement {
119
123
  }
120
124
 
121
125
  this.#checkedNodeIds.set(nodeId, nodeInfo.checkedValue)
122
- const payload: {path: string[]; value?: string} = {path: nodeInfo.path}
126
+ const payload: {path: string[]; nodeId?: string; value?: string} = {path: nodeInfo.path}
127
+ payload.nodeId = nodeId
123
128
  const dataValue = node.getAttribute('data-value')
124
129
  if (dataValue) payload.value = dataValue
125
130
  this.#checkedNodeFormPayloads.set(nodeId, payload)
@@ -371,12 +376,12 @@ export class FilterableTreeViewElement extends HTMLElement {
371
376
 
372
377
  if (requestWasFiltered) {
373
378
  this.#expandAllSubTrees()
374
- this.#applyAsyncHighlights(query)
379
+ if (this.#clientHighlightsEnabled) this.#applyAsyncHighlights(query)
375
380
  const hasResults = !!this.treeViewList?.querySelector('[role=treeitem]')
376
381
  this.noResultsMessage.toggleAttribute('hidden', hasResults)
377
382
  this.treeViewList?.toggleAttribute('hidden', !hasResults)
378
383
  } else {
379
- this.#removeHighlights()
384
+ if (this.#clientHighlightsEnabled) this.#removeHighlights()
380
385
  this.#restoreExpansionState()
381
386
  this.noResultsMessage.setAttribute('hidden', 'hidden')
382
387
  this.treeViewList?.removeAttribute('hidden')
@@ -699,7 +704,7 @@ export class FilterableTreeViewElement extends HTMLElement {
699
704
  this.treeViewList.removeAttribute('hidden')
700
705
  this.noResultsMessage.setAttribute('hidden', 'hidden')
701
706
 
702
- this.#applyHighlights(allRanges)
707
+ if (this.#clientHighlightsEnabled) this.#applyHighlights(allRanges)
703
708
  }
704
709
  }
705
710
 
@@ -29,10 +29,10 @@ module Primer
29
29
  # no slot provided
30
30
  return false if rows.empty? && columns.empty? && boxes.empty?
31
31
 
32
- if [rows, columns, boxes].count { |arr| !arr.empty? } == 1
32
+ if [rows, columns, boxes].one? { |arr| !arr.empty? }
33
33
  # only rows or columns or boxes are used
34
34
  true
35
- elsif [rows, columns, boxes].count { |arr| !arr.empty? } > 1
35
+ elsif [rows, columns, boxes].many? { |arr| !arr.empty? }
36
36
  # rows, columns and boxes are used together, which is not allowed
37
37
  raise ArgumentError, "You can't mix row, column and box slots"
38
38
  end
@@ -33,7 +33,7 @@ module Primer
33
33
  # @param scheme [Symbol] <%= one_of(Primer::OpenProject::InlineMessage::SCHEME_OPTIONS) %>
34
34
  # @param size [Symbol] <%= one_of(Primer::OpenProject::InlineMessage::SIZE_OPTIONS) %>
35
35
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
36
- def initialize(scheme:, size: DEFAULT_SIZE, **system_arguments) # rubocop:disable Lint/MissingSuper
36
+ def initialize(scheme:, size: DEFAULT_SIZE, **system_arguments)
37
37
  resolved_scheme = fetch_or_fallback(SCHEME_OPTIONS, scheme)
38
38
  resolved_size = fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)
39
39
 
@@ -4,6 +4,15 @@
4
4
  <%= trailing_action %>
5
5
  <% end %>
6
6
 
7
+ <% if @text_input_validation_message %>
8
+ <div class="FormControl-inlineValidation mt-1" id="<%= @text_input_validation_message_id %>">
9
+ <span class="FormControl-inlineValidation--visual">
10
+ <%= render(Primer::Beta::Octicon.new(icon: :"alert-fill", size: :xsmall, aria: { hidden: true })) %>
11
+ </span>
12
+ <span><%= @text_input_validation_message %></span>
13
+ </div>
14
+ <% end %>
15
+
7
16
  <% if caption %>
8
17
  <%= caption %>
9
18
  <% end %>
@@ -49,6 +49,19 @@ module Primer
49
49
  renders_one :text_input, lambda { |readonly: true, **system_arguments|
50
50
  deny_single_argument(:input_width, "Set the `input_width` on the `InputGroup`", **system_arguments)
51
51
  deny_single_argument(:caption, "Set the `caption` on the `InputGroup`", **system_arguments)
52
+
53
+ # Extract validation_message so it can be rendered outside the flex row.
54
+ # This prevents the trailing action from misaligning when validation is present.
55
+ @text_input_validation_message = system_arguments.delete(:validation_message)
56
+ if @text_input_validation_message
57
+ @text_input_validation_message_id = self.class.generate_id(base_name: "input-group-validation")
58
+ system_arguments[:invalid] = true
59
+ system_arguments[:aria] = merge_aria(
60
+ system_arguments,
61
+ { aria: { describedby: @text_input_validation_message_id } }
62
+ )
63
+ end
64
+
52
65
  system_arguments[:input_width] = @system_arguments[:input_width]
53
66
 
54
67
  system_arguments[:classes] = class_names(
@@ -288,7 +288,7 @@ module Primer
288
288
  end
289
289
 
290
290
  def render_mobile_menu?
291
- actions.count > 1
291
+ actions.many?
292
292
  end
293
293
 
294
294
  def show_state?
@@ -15,7 +15,7 @@ let PageHeaderElement = class PageHeaderElement extends HTMLElement {
15
15
  }
16
16
  };
17
17
  PageHeaderElement = __decorate([
18
- controller
18
+ controller('page-header')
19
19
  ], PageHeaderElement);
20
20
  if (!window.customElements.get('page-header')) {
21
21
  window.PageHeaderElement = PageHeaderElement;
@@ -1,6 +1,6 @@
1
1
  import {controller} from '@github/catalyst'
2
2
 
3
- @controller
3
+ @controller('page-header')
4
4
  class PageHeaderElement extends HTMLElement {
5
5
  menuItemClick(event: Event) {
6
6
  const currentTarget = event.currentTarget as HTMLButtonElement
@@ -269,9 +269,7 @@ module Primer
269
269
  key = :"page-#{page[:num]}-break"
270
270
  content = "…"
271
271
 
272
- props.merge!(
273
- role: "presentation"
274
- )
272
+ props[:role] = "presentation"
275
273
  end
276
274
 
277
275
  props[:class] = class_names("Page", props[:class])
@@ -35,7 +35,7 @@ module Primer
35
35
  def call
36
36
  render(@button) do |button|
37
37
  button.with_leading_visual_icon(icon: @icon)
38
- content
38
+ content || I18n.t("button_all_filters")
39
39
  end
40
40
  end
41
41
  end
@@ -7,6 +7,14 @@
7
7
 
8
8
  <%= render @collapsed_filter_trigger if @collapsed_filter_trigger.present? %>
9
9
 
10
+ <%= segmented_control %>
11
+
12
+ <% if @segmented_control_block.present? %>
13
+ <%= render(@mobile_segmented_control) do |control| %>
14
+ <% @segmented_control_block.call(control) %>
15
+ <% end %>
16
+ <% end %>
17
+
10
18
  <%= quick_sort %>
11
19
  <%= quick_group %>
12
20
  <% quick_filters.each do |quick_filter| %>
@@ -15,14 +23,6 @@
15
23
 
16
24
  <%= render(@mobile_filter_button) if @mobile_filter_button.present? %>
17
25
  <%= filter_button %>
18
-
19
- <%= segmented_control %>
20
-
21
- <% if @segmented_control_block.present? %>
22
- <%= render(@mobile_segmented_control) do |control| %>
23
- <% @segmented_control_block.call(control) %>
24
- <% end %>
25
- <% end %>
26
26
  </div>
27
27
 
28
28
  <div class="SubHeader-middlePane SubHeader-hiddenOnExpand">
@@ -40,7 +40,7 @@ module Primer
40
40
  @mobile_actions ||= []
41
41
  mobile_component = Primer::Beta::IconButton.new(display: MOBILE_ACTIONS_DISPLAY,
42
42
  **icon_args)
43
- @mobile_actions.push({ component: mobile_component, block: block})
43
+ @mobile_actions.push({ component: mobile_component, block: block })
44
44
 
45
45
  Primer::OpenProject::SubHeader::Button.new(display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
46
46
  end
@@ -65,9 +65,9 @@ module Primer
65
65
  mobile_component = Primer::OpenProject::SubHeader::Menu.new(icon_only: true,
66
66
  display: MOBILE_ACTIONS_DISPLAY,
67
67
  **kwargs)
68
- @mobile_actions.push({ component: mobile_component, block: block})
68
+ @mobile_actions.push({ component: mobile_component, block: block })
69
69
 
70
- Primer::OpenProject::SubHeader::Menu.new(icon_only: icon_only,display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
70
+ Primer::OpenProject::SubHeader::Menu.new(icon_only: icon_only, display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
71
71
  },
72
72
  }
73
73
  }
@@ -131,9 +131,10 @@ module Primer
131
131
  # To render custom content, call the `with_filter_component` method and pass a block that returns HTML.
132
132
  renders_one :filter_button, types: {
133
133
  button: {
134
- renders: lambda { |icon_only: false, leading_icon: :filter, mobile_label: I18n.t("button_filter"), **kwargs|
134
+ renders: lambda { |icon_only: false, leading_icon: :filter, mobile_label: I18n.t("button_all_filters"), **kwargs|
135
135
  kwargs[:mr] ||= 2
136
136
  kwargs[:icon] = leading_icon
137
+ kwargs[:scheme] ||= :invisible
137
138
 
138
139
  kwargs[:aria] ||= merge_aria(
139
140
  kwargs,
@@ -146,7 +147,7 @@ module Primer
146
147
  if icon_only
147
148
  Primer::Beta::IconButton.new(**icon_args)
148
149
  else
149
- @mobile_filter_button = Primer::Beta::IconButton.new(display: MOBILE_ACTIONS_DISPLAY,
150
+ @mobile_filter_button = Primer::Beta::IconButton.new(display: MOBILE_ACTIONS_DISPLAY,
150
151
  **icon_args)
151
152
 
152
153
  Primer::OpenProject::SubHeader::Button.new(display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
@@ -194,18 +195,18 @@ module Primer
194
195
  }
195
196
 
196
197
  renders_one :segmented_control, lambda { |**system_arguments, &block|
197
- deny_tag_argument(**system_arguments)
198
- system_arguments[:mr] ||= 2
199
- system_arguments = set_as_hidden_filter_target(system_arguments)
200
-
201
- @segmented_control_block = block
202
- @mobile_segmented_control = Primer::OpenProject::SubHeader::SegmentedControl.new(
203
- hide_labels: true,
204
- display: MOBILE_ACTIONS_DISPLAY,
205
- **system_arguments
206
- )
207
-
208
- Primer::OpenProject::SubHeader::SegmentedControl.new(display: DESKTOP_ACTIONS_DISPLAY, **system_arguments)
198
+ deny_tag_argument(**system_arguments)
199
+ system_arguments[:mr] ||= 2
200
+ system_arguments = set_as_hidden_filter_target(system_arguments)
201
+
202
+ @segmented_control_block = block
203
+ @mobile_segmented_control = Primer::OpenProject::SubHeader::SegmentedControl.new(
204
+ hide_labels: true,
205
+ display: MOBILE_ACTIONS_DISPLAY,
206
+ **system_arguments
207
+ )
208
+
209
+ Primer::OpenProject::SubHeader::SegmentedControl.new(display: DESKTOP_ACTIONS_DISPLAY, **system_arguments)
209
210
  }
210
211
 
211
212
  renders_one :text, lambda { |**system_arguments|
@@ -226,7 +227,7 @@ module Primer
226
227
 
227
228
  # @param collapsed_search [Boolean] When true, the search bar starts collapsed as an icon button on all screen sizes. Clicking expands it.
228
229
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
229
- def initialize(collapsed_search: false, **system_arguments)
230
+ def initialize(collapsed_search: true, **system_arguments)
230
231
  @collapsed_search = collapsed_search
231
232
  @system_arguments = system_arguments
232
233
  @system_arguments[:tag] = :"sub-header"
@@ -69,7 +69,7 @@ __decorate([
69
69
  targets
70
70
  ], SubHeaderElement.prototype, "filterExpandButton", void 0);
71
71
  SubHeaderElement = __decorate([
72
- controller
72
+ controller('sub-header')
73
73
  ], SubHeaderElement);
74
74
  if (!window.customElements.get('sub-header')) {
75
75
  window.SubHeaderElement = SubHeaderElement;
@@ -1,6 +1,6 @@
1
1
  import {controller, target, targets} from '@github/catalyst'
2
2
 
3
- @controller
3
+ @controller('sub-header')
4
4
  class SubHeaderElement extends HTMLElement {
5
5
  @target filterInput: HTMLInputElement
6
6
  @targets shownItemsOnExpandedFilter: HTMLElement[]
@@ -56,7 +56,7 @@ __decorate([
56
56
  target
57
57
  ], ZenModeButtonElement.prototype, "button", void 0);
58
58
  ZenModeButtonElement = __decorate([
59
- controller
59
+ controller('zen-mode-button')
60
60
  ], ZenModeButtonElement);
61
61
  if (!window.customElements.get('zen-mode-button')) {
62
62
  window.ZenModeButtonElement = ZenModeButtonElement;
@@ -1,6 +1,6 @@
1
1
  import {controller, target} from '@github/catalyst'
2
2
 
3
- @controller
3
+ @controller('zen-mode-button')
4
4
  class ZenModeButtonElement extends HTMLElement {
5
5
  @target button: HTMLElement
6
6
  inZenMode = false
@@ -165,15 +165,32 @@ module Primer
165
165
  def index
166
166
  query = params[:query].to_s.strip
167
167
  select_variant = (params[:select_variant].presence || "multiple").to_sym
168
+ server_highlights = params[:server_highlights] == "true"
168
169
  nodes = TREE.filter_map { |node| node.filter(query) }
169
170
 
170
- render locals: {
171
- nodes: nodes,
172
- query: query,
173
- select_variant: select_variant
174
- }
171
+ if server_highlights && query.present?
172
+ html = render_to_string locals: { nodes:, query:, select_variant: }
173
+ render html: inject_highlights(html, query)
174
+ else
175
+ render locals: { nodes:, query:, select_variant: }
176
+ end
175
177
  end
176
178
 
179
+ private
180
+
181
+ # Post-processes the rendered tree HTML to wrap matching label text in <mark> tags.
182
+ # This avoids html_safety issues with passing markup through ViewComponent's label slot.
183
+ # Only called in the demo endpoint when server_highlights=true is requested.
184
+ def inject_highlights(html, query)
185
+ escaped_query = Regexp.escape(CGI.escapeHTML(query))
186
+ html.gsub(
187
+ /(<span class="TreeViewItemContentText">)([^<]*)(#{escaped_query})([^<]*)(<\/span>)/i,
188
+ '\1\2<mark>\3</mark>\4\5'
189
+ ).html_safe
190
+ end
191
+
192
+ public
193
+
177
194
  def async_form_tree
178
195
  query = params[:query].to_s.strip
179
196
  name = params[:name].to_s.presence || "characters"
@@ -5,6 +5,7 @@ en:
5
5
  button_close: "Close"
6
6
  button_delete: "Delete"
7
7
  button_delete_permanently: "Delete permanently"
8
+ button_all_filters: "All filters"
8
9
  button_filter: "Filter"
9
10
  button_save: "Save"
10
11
 
@@ -5,7 +5,7 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 87
8
+ MINOR = 89
9
9
  PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -0,0 +1,12 @@
1
+ <div style="max-width: 400px">
2
+ <%= render(Primer::Alpha::TreeView.new) do |tree_view| %>
3
+ <% tree_view.with_sub_tree(label: "src", expanded: true, select_variant: :single) do |sub_tree| %>
4
+ <% sub_tree.with_leaf(label: "button.rb", select_variant: :single) %>
5
+ <% sub_tree.with_leaf(label: "icon_button.rb", current: true, select_variant: :single) %>
6
+ <% end %>
7
+
8
+ <% tree_view.with_leaf(label: "action_menu.rb", select_variant: :single) %>
9
+ <% tree_view.with_leaf(label: "action_menu.rb", select_variant: :single) %>
10
+ <% tree_view.with_leaf(label: "action_menu.rb", select_variant: :single) %>
11
+ <% end %>
12
+ </div>
@@ -1,15 +1,15 @@
1
1
  <%= form_with(url: primer_view_components.generic_form_submission_path(format: :json)) do |f| %>
2
2
  <%= render(Primer::Alpha::Stack.new) do %>
3
3
  <%= render(Primer::Alpha::TreeView.new(form_arguments: { builder: f, name: "folder_structure" })) do |tree| %>
4
- <% tree.with_sub_tree(label: "src", expanded: expanded, select_variant: select_variant, value: 0) do |sub_tree| %>
5
- <% sub_tree.with_leaf(label: "button.rb", select_variant: select_variant, value: 1, checked: true) %>
6
- <% sub_tree.with_leaf(label: "icon_button.rb", current: true, select_variant: select_variant, value: 2) %>
4
+ <% tree.with_sub_tree(label: "src", expanded: expanded, select_variant: select_variant, value: 0, data: { node_id: "src" }) do |sub_tree| %>
5
+ <% sub_tree.with_leaf(label: "button.rb", select_variant: select_variant, value: 1, checked: true, data: { node_id: "src-button-rb" }) %>
6
+ <% sub_tree.with_leaf(label: "icon_button.rb", current: true, select_variant: select_variant, value: 2, data: { node_id: "src-icon-button-rb" }) %>
7
7
  <% end %>
8
8
 
9
- <% tree.with_leaf(label: "action_menu.rb", select_variant: select_variant, value: 3) %>
10
- <% tree.with_sub_tree(label: "Docs & legal requirements", select_variant: select_variant, value: 4) do |sub_tree| %>
11
- <% sub_tree.with_leaf(label: "Readme.md", select_variant: select_variant, value: 5) %>
12
- <% sub_tree.with_leaf(label: "Copyright.md", select_variant: select_variant, value: 6) %>
9
+ <% tree.with_leaf(label: "action_menu.rb", select_variant: select_variant, value: 3, data: { node_id: "action-menu-rb" }) %>
10
+ <% tree.with_sub_tree(label: "Docs & legal requirements", select_variant: select_variant, value: 4, data: { node_id: "docs" }) do |sub_tree| %>
11
+ <% sub_tree.with_leaf(label: "Readme.md", select_variant: select_variant, value: 5, data: { node_id: "docs-readme-md" }) %>
12
+ <% sub_tree.with_leaf(label: "Copyright.md", select_variant: select_variant, value: 6, data: { node_id: "docs-copyright-md" }) %>
13
13
  <% end %>
14
14
  <% end %>
15
15
 
@@ -197,6 +197,12 @@ module Primer
197
197
  })
198
198
  end
199
199
 
200
+ # @label Doubled paths
201
+ # @hidden
202
+ def doubled_path
203
+ render_with_template
204
+ end
205
+
200
206
  private
201
207
 
202
208
  def coerce_bool(value)
@@ -0,0 +1,4 @@
1
+ <%= render(Primer::OpenProject::FilterableTreeView.new(
2
+ show_search_highlighting: false,
3
+ src: primer_view_components.filterable_tree_view_items_tree_path(select_variant: select_variant, server_highlights: true)
4
+ )) %>
@@ -123,6 +123,18 @@ module Primer
123
123
  })
124
124
  end
125
125
 
126
+ # @label Async (with server-side highlighting)
127
+ #
128
+ # Demonstrates `show_search_highlighting: false`: the client skips its own highlight logic
129
+ # and the server is responsible for wrapping matching text in `<mark>` (or other HTML) elements.
130
+ #
131
+ # @param select_variant [Symbol] select [multiple, single, none]
132
+ def async_server_highlights(select_variant: :single)
133
+ render_with_template(locals: {
134
+ select_variant: select_variant.to_sym
135
+ })
136
+ end
137
+
126
138
  # @label Async form input
127
139
  def async_form_input
128
140
  render_with_template
@@ -71,6 +71,15 @@ module Primer
71
71
  menu.with_caption { "Some caption" }
72
72
  end
73
73
  end
74
+
75
+ # @label With error message
76
+ # @snapshot
77
+ def with_error_message
78
+ render(Primer::OpenProject::InputGroup.new) do |menu|
79
+ menu.with_text_input(name: "a name", label: "My input group", validation_message: "Custom validation message", value: "Copyable value")
80
+ menu.with_trailing_action_clipboard_copy_button(id: "button", value: "Copyable value", aria: { label: "Copy some text" })
81
+ end
82
+ end
74
83
  end
75
84
  end
76
85
  end
@@ -1,7 +1,7 @@
1
- <%= render(Primer::OpenProject::SubHeader.new(collapsed_search: true)) do |component|
1
+ <%= render(Primer::OpenProject::SubHeader.new) do |component|
2
2
  component.with_filter_input(name: "filter", label: "Filter")
3
3
 
4
- component.with_filter_button(mobile_label: "All filters") do
4
+ component.with_filter_button do
5
5
  "All filters"
6
6
  end
7
7
 
@@ -22,16 +22,17 @@ module Primer
22
22
  show_filter_button: true,
23
23
  show_quick_filter: true,
24
24
  show_action_button: true,
25
- collapsed_search: false,
25
+ collapsed_search: true,
26
26
  text: nil,
27
27
  value: nil
28
28
  )
29
- render_with_template(locals: {show_filter_input: show_filter_input,
30
- show_clear_button: show_clear_button,
31
- show_filter_button: show_filter_button,
32
- show_quick_filter: show_quick_filter,
33
- show_action_button: show_action_button,
34
- collapsed_search: collapsed_search,
29
+ bool = ActiveModel::Type::Boolean.new
30
+ render_with_template(locals: {show_filter_input: bool.cast(show_filter_input),
31
+ show_clear_button: bool.cast(show_clear_button),
32
+ show_filter_button: bool.cast(show_filter_button),
33
+ show_quick_filter: bool.cast(show_quick_filter),
34
+ show_action_button: bool.cast(show_action_button),
35
+ collapsed_search: bool.cast(collapsed_search),
35
36
  text: text,
36
37
  value: value})
37
38
  end
@@ -42,7 +43,6 @@ module Primer
42
43
  component.with_filter_input(name: "filter", label: "Filter")
43
44
  component.with_filter_button do |button|
44
45
  button.with_trailing_visual_counter(count: "15")
45
- "Filter"
46
46
  end
47
47
 
48
48
  component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
@@ -93,11 +93,11 @@ module Primer
93
93
  end
94
94
  end
95
95
 
96
- # @label With collapsed search
97
- def collapsed_search
98
- render(Primer::OpenProject::SubHeader.new(collapsed_search: true)) do |component|
96
+ # @label With expanded search
97
+ def expanded_search
98
+ render(Primer::OpenProject::SubHeader.new(collapsed_search: false)) do |component|
99
99
  component.with_filter_input(name: "filter", label: "Filter")
100
- component.with_filter_button do |button|
100
+ component.with_filter_button(scheme: :default) do |button|
101
101
  button.with_trailing_visual_counter(count: "15")
102
102
  "Filter"
103
103
  end
@@ -6188,25 +6188,25 @@
6188
6188
  {
6189
6189
  "name": "filter_input_arguments",
6190
6190
  "type": "Hash",
6191
- "default": "`DEFAULT_FILTER_INPUT_ARGUMENTS.dup`",
6191
+ "default": "`{}`",
6192
6192
  "description": "Arguments that will be passed to the [TextField](/components/alpha/textfield) component."
6193
6193
  },
6194
6194
  {
6195
6195
  "name": "filter_mode_control_arguments",
6196
6196
  "type": "Hash",
6197
- "default": "`DEFAULT_FILTER_MODE_CONTROL_ARGUMENTS.dup`",
6197
+ "default": "`{}`",
6198
6198
  "description": "Arguments that will be passed to the [SegmentedControl](/components/alpha/segmentedcontrol) component."
6199
6199
  },
6200
6200
  {
6201
6201
  "name": "include_sub_items_check_box_arguments",
6202
6202
  "type": "Hash",
6203
- "default": "`DEFAULT_INCLUDE_SUB_ITEMS_CHECK_BOX_ARGUMENTS.dup`",
6203
+ "default": "`{}`",
6204
6204
  "description": "Arguments that will be passed to the [CheckBox](/components/alpha/checkbox) component."
6205
6205
  },
6206
6206
  {
6207
6207
  "name": "no_results_node_arguments",
6208
6208
  "type": "Hash",
6209
- "default": "`DEFAULT_NO_RESULTS_NODE_ARGUMENTS.dup`",
6209
+ "default": "`{}`",
6210
6210
  "description": "Arguments that will be passed to a [TreeView::LeafNode](/components/alpha/treeviewleafnode) component that appears when no items match the filter criteria."
6211
6211
  }
6212
6212
  ]
@@ -6600,12 +6600,12 @@
6600
6600
  ]
6601
6601
  },
6602
6602
  {
6603
- "component": "OpenProject::SubHeader::QuickActionComponent",
6603
+ "component": "OpenProject::SubHeader::QuickAction",
6604
6604
  "status": "open_project",
6605
6605
  "a11y_reviewed": false,
6606
- "short_name": "OpenProjectSubHeaderQuickActionComponent",
6607
- "source": "https://github.com/primer/view_components/tree/main/app/components/primer/open_project/sub_header/quick_filter.rb",
6608
- "lookbook": "https://primer.style/view-components/lookbook/inspect/primer/open_project/sub_header/quick_filter/default/",
6606
+ "short_name": "OpenProjectSubHeaderQuickAction",
6607
+ "source": "https://github.com/primer/view_components/tree/main/app/components/primer/open_project/sub_header/quick_action_component.rb",
6608
+ "lookbook": "https://primer.style/view-components/lookbook/inspect/primer/open_project/sub_header/quick_action/default/",
6609
6609
  "parameters": []
6610
6610
  },
6611
6611
  {