From fa1a5fd5aab3f9f1bd659e0a096bca391c8c9a39 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 30 Apr 2026 15:01:44 +0200 Subject: [PATCH 1/5] Add option to collapse the subHeader search on desktop as well --- .changeset/stupid-signs-raise.md | 5 ++ .../primer/open_project/sub_header.html.erb | 10 ++-- .../primer/open_project/sub_header.pcss | 12 +++-- .../primer/open_project/sub_header.rb | 47 ++++++++++--------- .../primer/open_project/sub_header_element.ts | 15 ++---- .../primer/open_project/sub_header_preview.rb | 19 +++++++- 6 files changed, 64 insertions(+), 44 deletions(-) create mode 100644 .changeset/stupid-signs-raise.md diff --git a/.changeset/stupid-signs-raise.md b/.changeset/stupid-signs-raise.md new file mode 100644 index 0000000000..ba39127e7d --- /dev/null +++ b/.changeset/stupid-signs-raise.md @@ -0,0 +1,5 @@ +--- +'@openproject/primer-view-components': minor +--- + +Extend SubHeader to support QuickFilters diff --git a/app/components/primer/open_project/sub_header.html.erb b/app/components/primer/open_project/sub_header.html.erb index b54948e815..c4f0cfb633 100644 --- a/app/components/primer/open_project/sub_header.html.erb +++ b/app/components/primer/open_project/sub_header.html.erb @@ -2,12 +2,10 @@
<%= render @filter_container do %> <%= filter_input %> - <%= render @mobile_filter_cancel do - I18n.t("button_cancel") - end if @mobile_filter_cancel.present? %> + <%= render @collapsed_filter_cancel if @collapsed_filter_cancel.present? %> <% end if filter_input.present? %> - <%= render @mobile_filter_trigger if @mobile_filter_trigger.present? %> + <%= render @collapsed_filter_trigger if @collapsed_filter_trigger.present? %> <%= render(@mobile_filter_button) if @mobile_filter_button.present? %> @@ -22,11 +20,11 @@ <% end %>
-
+
<%= text %>
-
+
<% actions.each do |action| %> <%= action %> <% end %> diff --git a/app/components/primer/open_project/sub_header.pcss b/app/components/primer/open_project/sub_header.pcss index 2a1b7300d9..5065f30da6 100644 --- a/app/components/primer/open_project/sub_header.pcss +++ b/app/components/primer/open_project/sub_header.pcss @@ -8,10 +8,6 @@ margin-bottom: var(--base-size-16); } -.SubHeader--expandedSearch { - grid-template-areas: "left left left" "bottom bottom bottom"; -} - .SubHeader-rightPane { grid-area: right; display: flex; @@ -60,6 +56,14 @@ grid-template-columns: 1fr auto; } + .SubHeader--expandedSearch { + grid-template-areas: "left left" "middle middle" "bottom bottom"; + } + + .SubHeader--expandedSearch .SubHeader-hiddenOnExpand { + display: none !important; + } + .SubHeader--emptyLeftPane { grid-template-areas: "middle middle right" "bottom bottom bottom"; grid-template-columns: auto 1fr auto; diff --git a/app/components/primer/open_project/sub_header.rb b/app/components/primer/open_project/sub_header.rb index fb60d9f1d7..2374993359 100644 --- a/app/components/primer/open_project/sub_header.rb +++ b/app/components/primer/open_project/sub_header.rb @@ -7,8 +7,8 @@ module OpenProject class SubHeader < Primer::Component status :open_project - HIDDEN_FILTER_TARGET_SELECTOR = "sub-header.hiddenItemsOnExpandedFilter" SHOWN_FILTER_TARGET_SELECTOR = "sub-header.shownItemsOnExpandedFilter" + FILTER_EXPAND_BUTTON_TARGET_SELECTOR = "sub-header.filterExpandButton" MOBILE_ACTIONS_DISPLAY = [:flex, :none].freeze DESKTOP_ACTIONS_DISPLAY = [:none, :flex].freeze @@ -101,18 +101,23 @@ class SubHeader < Primer::Component system_arguments[:data][:action] += " input:sub-header#toggleFilterInputClearButton focus:sub-header#toggleFilterInputClearButton" end - @mobile_filter_trigger = Primer::Beta::IconButton.new(icon: system_arguments[:leading_visual][:icon], - display: [:inline_flex, :none], - aria: { label: label }, - mr: 2, - "data-action": "click:sub-header#expandFilterInput", - "data-targets": HIDDEN_FILTER_TARGET_SELECTOR) + trigger_display = @collapsed_search ? :inline_flex : [:inline_flex, :none] - @mobile_filter_cancel = Primer::Beta::Button.new(scheme: :invisible, - display: :none, - data: { - targets: SHOWN_FILTER_TARGET_SELECTOR, - action: "click:sub-header#collapseFilterInput"}) + @collapsed_filter_trigger = Primer::Beta::IconButton.new(icon: system_arguments[:leading_visual][:icon], + display: trigger_display, + aria: { label: label }, + mr: 2, + "data-action": "click:sub-header#expandFilterInput", + "data-targets": FILTER_EXPAND_BUTTON_TARGET_SELECTOR) + + @collapsed_filter_cancel = Primer::Beta::IconButton.new(icon: :x, + "aria-label": I18n.t(:button_cancel), + scheme: :invisible, + display: :none, + data: { + targets: SHOWN_FILTER_TARGET_SELECTOR, + action: "click:sub-header#collapseFilterInput" + }) Primer::Alpha::TextField.new(name: name, label: label, **system_arguments) @@ -196,15 +201,18 @@ class SubHeader < Primer::Component } + # @param collapsed_search [Boolean] When true, the search bar starts collapsed as an icon button on all screen sizes. Clicking expands it. # @param system_arguments [Hash] <%= link_to_system_arguments_docs %> - def initialize(**system_arguments) + def initialize(collapsed_search: false, **system_arguments) + @collapsed_search = collapsed_search @system_arguments = system_arguments @system_arguments[:tag] = :"sub-header" + filter_container_display = collapsed_search ? :none : DESKTOP_ACTIONS_DISPLAY + @filter_container = Primer::BaseComponent.new(tag: :div, classes: "SubHeader-filterContainer", - display: DESKTOP_ACTIONS_DISPLAY, - + display: filter_container_display, mr: 2, data: { targets: SHOWN_FILTER_TARGET_SELECTOR }) @@ -222,14 +230,7 @@ def before_render end def set_as_hidden_filter_target(system_arguments) - system_arguments[:data] ||= {} - system_arguments[:data] = merge_data( - system_arguments, { - data: { - targets: HIDDEN_FILTER_TARGET_SELECTOR, - } - } - ) + system_arguments[:classes] = class_names(system_arguments[:classes], "SubHeader-hiddenOnExpand") system_arguments end end diff --git a/app/components/primer/open_project/sub_header_element.ts b/app/components/primer/open_project/sub_header_element.ts index bc1aa529d5..b272a84445 100644 --- a/app/components/primer/open_project/sub_header_element.ts +++ b/app/components/primer/open_project/sub_header_element.ts @@ -3,8 +3,8 @@ import {controller, target, targets} from '@github/catalyst' @controller class SubHeaderElement extends HTMLElement { @target filterInput: HTMLInputElement - @targets hiddenItemsOnExpandedFilter: HTMLElement[] @targets shownItemsOnExpandedFilter: HTMLElement[] + @targets filterExpandButton: HTMLElement[] connectedCallback() { this.setupFilterInputClearButton() @@ -28,28 +28,23 @@ class SubHeaderElement extends HTMLElement { } expandFilterInput() { - for (const item of this.hiddenItemsOnExpandedFilter) { - item.classList.add('d-none') - } - for (const item of this.shownItemsOnExpandedFilter) { item.classList.remove('d-none') } - + for (const item of this.filterExpandButton) { + item.classList.add('d-none') + } this.classList.add('SubHeader--expandedSearch') - this.filterInput.focus() } collapseFilterInput() { - for (const item of this.hiddenItemsOnExpandedFilter) { + for (const item of this.filterExpandButton) { item.classList.remove('d-none') } - for (const item of this.shownItemsOnExpandedFilter) { item.classList.add('d-none') } - this.classList.remove('SubHeader--expandedSearch') } diff --git a/previews/primer/open_project/sub_header_preview.rb b/previews/primer/open_project/sub_header_preview.rb index 67c6102ec3..2d29d350aa 100644 --- a/previews/primer/open_project/sub_header_preview.rb +++ b/previews/primer/open_project/sub_header_preview.rb @@ -12,6 +12,7 @@ class SubHeaderPreview < ViewComponent::Preview # @param show_filter_button toggle # @param show_action_button toggle # @param show_clear_button toggle + # @param collapsed_search toggle # @param text text # @param value text def playground( @@ -19,10 +20,11 @@ def playground( show_clear_button: true, show_filter_button: true, show_action_button: true, + collapsed_search: false, text: nil, value: nil ) - render(Primer::OpenProject::SubHeader.new) do |component| + render(Primer::OpenProject::SubHeader.new(collapsed_search: collapsed_search)) do |component| component.with_filter_input( name: "filter", label: "Filter", @@ -98,6 +100,21 @@ def segmented_control end end + # @label With collapsed search + def collapsed_search + render(Primer::OpenProject::SubHeader.new(collapsed_search: true)) do |component| + component.with_filter_input(name: "filter", label: "Filter") + component.with_filter_button do |button| + button.with_trailing_visual_counter(count: "15") + "Filter" + end + + component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do + "Create" + end + end + end + # @label With a custom area below def bottom_pane render_with_template(locals: {}) From d3c19d6a93afc2bdb26656b520f2d1c327a83a12 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 30 Apr 2026 15:31:32 +0200 Subject: [PATCH 2/5] Offer dedicated quick filter slots --- .../primer/open_project/sub_header.html.erb | 9 +++- .../primer/open_project/sub_header.pcss | 7 ++- .../primer/open_project/sub_header.rb | 24 +++++++++- .../primer/open_project/sub_header_preview.rb | 5 ++ .../sub_header_preview/quick_filters.html.erb | 47 +++++++++++++++++++ 5 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 previews/primer/open_project/sub_header_preview/quick_filters.html.erb diff --git a/app/components/primer/open_project/sub_header.html.erb b/app/components/primer/open_project/sub_header.html.erb index c4f0cfb633..46e666080f 100644 --- a/app/components/primer/open_project/sub_header.html.erb +++ b/app/components/primer/open_project/sub_header.html.erb @@ -7,9 +7,14 @@ <%= render @collapsed_filter_trigger if @collapsed_filter_trigger.present? %> - <%= render(@mobile_filter_button) if @mobile_filter_button.present? %> + <%= render(Primer::BaseComponent.new(tag: :div, display: :flex)) do %> + <% quick_filters.each do |quick_filter| %> + <%= quick_filter %> + <% end %> - <%= filter_button %> + <%= render(@mobile_filter_button) if @mobile_filter_button.present? %> + <%= filter_button %> + <% end %> <%= segmented_control %> diff --git a/app/components/primer/open_project/sub_header.pcss b/app/components/primer/open_project/sub_header.pcss index 5065f30da6..af724f5952 100644 --- a/app/components/primer/open_project/sub_header.pcss +++ b/app/components/primer/open_project/sub_header.pcss @@ -4,7 +4,7 @@ display: grid; grid-template-areas: "left middle right" "bottom bottom bottom"; grid-template-columns: auto 1fr auto; - align-items: center; + align-items: baseline; margin-bottom: var(--base-size-16); } @@ -18,6 +18,7 @@ .SubHeader-middlePane { grid-area: middle; text-align: center; + white-space: nowrap; } .SubHeader-bottomPane { @@ -28,6 +29,8 @@ grid-area: left; display: flex; align-items: center; + flex-wrap: wrap; + row-gap: var(--stack-gap-normal); width: 100%; /* Since the container is not full width (due to the grid around it) @@ -50,7 +53,7 @@ display: none; } -@media (max-width: 543.98px) { +@media (max-width: 767.98px) { .SubHeader { grid-template-areas: "left right" "middle middle" "bottom bottom"; grid-template-columns: 1fr auto; diff --git a/app/components/primer/open_project/sub_header.rb b/app/components/primer/open_project/sub_header.rb index 2374993359..cc778673dc 100644 --- a/app/components/primer/open_project/sub_header.rb +++ b/app/components/primer/open_project/sub_header.rb @@ -170,6 +170,18 @@ class SubHeader < Primer::Component } } + # Quick filters shown in the left pane next to the search bar (0–5 items). + # Hidden on mobile. Requires all_filters_button to be set when used. + # Supports ActionMenus, Buttons, IconButtons, SelectPanels, and SegmentedControls inside the block. + renders_many :quick_filters, lambda { |**kwargs| + deny_tag_argument(**kwargs) + kwargs[:tag] = :div + kwargs[:mr] ||= 2 + kwargs[:display] = DESKTOP_ACTIONS_DISPLAY + + Primer::BaseComponent.new(**kwargs) + } + renders_one :segmented_control, lambda { |**system_arguments, &block| deny_tag_argument(**system_arguments) system_arguments[:mr] ||= 2 @@ -187,6 +199,7 @@ class SubHeader < Primer::Component renders_one :text, lambda { |**system_arguments| system_arguments[:font_weight] ||= :bold + system_arguments[:mx] ||= 2 Primer::Beta::Text.new(**system_arguments) } @@ -200,7 +213,6 @@ class SubHeader < Primer::Component Primer::BaseComponent.new(**system_arguments) } - # @param collapsed_search [Boolean] When true, the search bar starts collapsed as an icon button on all screen sizes. Clicking expands it. # @param system_arguments [Hash] <%= link_to_system_arguments_docs %> def initialize(collapsed_search: false, **system_arguments) @@ -223,9 +235,17 @@ def initialize(collapsed_search: false, **system_arguments) end def before_render + if quick_filters.any? && filter_button.nil? + raise ArgumentError, "You must provide a filter_button when using quick_filters." + end + + if quick_filters.size > 5 + raise ArgumentError, "SubHeader supports a maximum of 5 quick_filters, got #{quick_filters.size}." + end + @system_arguments[:classes] = class_names( @system_arguments[:classes], - "SubHeader--emptyLeftPane" => !segmented_control? && !filter_button && !filter_input + "SubHeader--emptyLeftPane" => !segmented_control? && !filter_button && !filter_input && quick_filters.empty? ) end diff --git a/previews/primer/open_project/sub_header_preview.rb b/previews/primer/open_project/sub_header_preview.rb index 2d29d350aa..927c3545de 100644 --- a/previews/primer/open_project/sub_header_preview.rb +++ b/previews/primer/open_project/sub_header_preview.rb @@ -115,6 +115,11 @@ def collapsed_search end end + # @label With QuickFilters + def quick_filters + render_with_template(locals: {}) + end + # @label With a custom area below def bottom_pane render_with_template(locals: {}) diff --git a/previews/primer/open_project/sub_header_preview/quick_filters.html.erb b/previews/primer/open_project/sub_header_preview/quick_filters.html.erb new file mode 100644 index 0000000000..a9e2fd364f --- /dev/null +++ b/previews/primer/open_project/sub_header_preview/quick_filters.html.erb @@ -0,0 +1,47 @@ +<%= render(Primer::OpenProject::SubHeader.new(collapsed_search: true)) do |component| + component.with_filter_input(name: "filter", label: "Filter") + + component.with_filter_button(mobile_label: "All filters") do + "All filters" + end + + component.with_quick_filter do + render(Primer::Alpha::SelectPanel.new(title: "Version", + select_variant: :single, + fetch_strategy: :local, + dynamic_label: true, + dynamic_label_prefix: "Version")) do |panel| + panel.with_show_button { "Version" } + panel.with_item(label: "1.0") + panel.with_item(label: "2.0") + panel.with_item(label: "2.1") + end + end + + component.with_quick_filter do + render(Primer::Alpha::SelectPanel.new(title: "Assignee", + select_variant: :multiple, + fetch_strategy: :local, + dynamic_label: true, + dynamic_label_prefix: "Assignee")) do |panel| + panel.with_show_button { "Assignee" } + panel.with_avatar_item(username: "Alice", src: "https://avatars.githubusercontent.com/u/1?v=4") + panel.with_avatar_item(username: "Bob", src: "https://avatars.githubusercontent.com/u/2?v=4") + panel.with_avatar_item(username: "Carol", src: "https://avatars.githubusercontent.com/u/3?v=4") + panel.with_avatar_item(username: "Dave", src: "https://avatars.githubusercontent.com/u/4?v=4") + panel.with_avatar_item(username: "Eve", src: "https://avatars.githubusercontent.com/u/5?v=4") + panel.with_avatar_item(username: "Frank", src: "https://avatars.githubusercontent.com/u/6?v=4") + panel.with_avatar_item(username: "Grace", src: "https://avatars.githubusercontent.com/u/7?v=4") + panel.with_avatar_item(username: "Heidi", src: "https://avatars.githubusercontent.com/u/8?v=4") + panel.with_avatar_item(username: "Ivan", src: "https://avatars.githubusercontent.com/u/9?v=4") + panel.with_avatar_item(username: "Judy", src: "https://avatars.githubusercontent.com/u/10?v=4") + panel.with_footer(show_divider: true) do + render(Primer::Beta::Button.new(scheme: :primary)) { "Apply" } + end + end + end + + component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do + "Create" + end +end %> From da032e1b40dee610544b83ff333e1766b949ce4b Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Mon, 4 May 2026 08:24:10 +0200 Subject: [PATCH 3/5] Add tests for new SubHeader behaviour --- .../primer/open_project/sub_header.pcss | 4 +- .../primer/open_project/sub_header_test.rb | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/app/components/primer/open_project/sub_header.pcss b/app/components/primer/open_project/sub_header.pcss index af724f5952..80f4f645e3 100644 --- a/app/components/primer/open_project/sub_header.pcss +++ b/app/components/primer/open_project/sub_header.pcss @@ -30,7 +30,7 @@ display: flex; align-items: center; flex-wrap: wrap; - row-gap: var(--stack-gap-normal); + row-gap: var(--base-size-16); width: 100%; /* Since the container is not full width (due to the grid around it) @@ -83,6 +83,6 @@ } .SubHeader-middlePane:has(> *) { - margin-top: var(--stack-gap-normal); + margin-top: var(--base-size-16); } } diff --git a/test/components/primer/open_project/sub_header_test.rb b/test/components/primer/open_project/sub_header_test.rb index 59362e3af8..a2cb4b4c14 100644 --- a/test/components/primer/open_project/sub_header_test.rb +++ b/test/components/primer/open_project/sub_header_test.rb @@ -228,4 +228,79 @@ def test_does_not_render_input_events_when_show_clear_button_is_not_set ) assert_no_selector(".FormControl-input-trailingAction[data-action=\"click:primer-text-field#clearContents\"]") end + + def test_renders_collapsed_search + render_inline(Primer::OpenProject::SubHeader.new(collapsed_search: true)) do |component| + component.with_filter_input(name: "filter", label: "Filter") + end + + assert_selector(".SubHeader") + # Trigger is rendered and wired to expand + assert_selector("[data-action='click:sub-header#expandFilterInput']") + # Filter container starts hidden on all screen sizes (d-none, no d-sm-flex) + assert_selector(".SubHeader-filterContainer.d-none") + assert_no_selector(".SubHeader-filterContainer.d-sm-flex") + end + + def test_collapsed_search_trigger_visible_on_all_screen_sizes + render_inline(Primer::OpenProject::SubHeader.new(collapsed_search: true)) do |component| + component.with_filter_input(name: "filter", label: "Filter") + end + + # Without collapsed_search, the trigger has d-sm-none (hidden on desktop). + # With collapsed_search, it must not have d-sm-none. + assert_no_selector("[data-action='click:sub-header#expandFilterInput'].d-sm-none") + end + + def test_default_search_trigger_hidden_on_desktop + render_inline(Primer::OpenProject::SubHeader.new) do |component| + component.with_filter_input(name: "filter", label: "Filter") + end + + # Without collapsed_search the trigger is mobile-only (d-sm-none) + assert_selector("[data-action='click:sub-header#expandFilterInput'].d-sm-none") + end + + def test_renders_quick_filters + render_inline(Primer::OpenProject::SubHeader.new) do |component| + component.with_filter_input(name: "filter", label: "Filter") + component.with_filter_button { "Filter" } + component.with_quick_filter { "Status".html_safe } + component.with_quick_filter { "Assignee".html_safe } + end + + assert_selector(".SubHeader-leftPane .MyQuickFilter", count: 2) + end + + def test_quick_filters_are_hidden_on_mobile + render_inline(Primer::OpenProject::SubHeader.new) do |component| + component.with_filter_button { "Filter" } + component.with_quick_filter { "Status".html_safe } + end + + # Each quick_filter wrapper has d-none d-sm-flex (hidden on mobile, visible on desktop) + assert_selector(".d-none.d-sm-flex .MyQuickFilter") + end + + def test_quick_filters_require_filter_button + err = assert_raises ArgumentError do + render_inline(Primer::OpenProject::SubHeader.new) do |component| + component.with_filter_input(name: "filter", label: "Filter") + component.with_quick_filter { "Status" } + end + end + + assert_equal "You must provide a filter_button when using quick_filters.", err.message + end + + def test_quick_filters_maximum_is_five + err = assert_raises ArgumentError do + render_inline(Primer::OpenProject::SubHeader.new) do |component| + component.with_filter_button { "Filter" } + 6.times { |i| component.with_quick_filter { "Filter #{i}" } } + end + end + + assert_equal "SubHeader supports a maximum of 5 quick_filters, got 6.", err.message + end end From 692db2c9bbd19d636865e9ec32f00300db34e397 Mon Sep 17 00:00:00 2001 From: OpenProject CI Date: Thu, 7 May 2026 08:28:53 +0000 Subject: [PATCH 4/5] Generating static files --- static/arguments.json | 6 ++++++ static/constants.json | 2 +- static/info_arch.json | 37 +++++++++++++++++++++++++++++++++++++ static/previews.json | 26 ++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/static/arguments.json b/static/arguments.json index 11ef38e9bf..c36549263c 100644 --- a/static/arguments.json +++ b/static/arguments.json @@ -6473,6 +6473,12 @@ "source": "https://github.com/primer/view_components/tree/main/app/components/primer/open_project/sub_header.rb", "lookbook": "https://primer.style/view-components/lookbook/inspect/primer/open_project/sub_header/default/", "parameters": [ + { + "name": "collapsed_search", + "type": "Boolean", + "default": "`false`", + "description": "When true, the search bar starts collapsed as an icon button on all screen sizes. Clicking expands it." + }, { "name": "system_arguments", "type": "Hash", diff --git a/static/constants.json b/static/constants.json index 4c0c62290c..b63e0c7118 100644 --- a/static/constants.json +++ b/static/constants.json @@ -1978,8 +1978,8 @@ "none", "flex" ], + "FILTER_EXPAND_BUTTON_TARGET_SELECTOR": "sub-header.filterExpandButton", "GeneratedSlotMethods": "Primer::OpenProject::SubHeader::GeneratedSlotMethods", - "HIDDEN_FILTER_TARGET_SELECTOR": "sub-header.hiddenItemsOnExpandedFilter", "MOBILE_ACTIONS_DISPLAY": [ "flex", "none" diff --git a/static/info_arch.json b/static/info_arch.json index 4807fdfdec..9a67091636 100644 --- a/static/info_arch.json +++ b/static/info_arch.json @@ -21649,6 +21649,12 @@ "source": "https://github.com/primer/view_components/tree/main/app/components/primer/open_project/sub_header.rb", "lookbook": "https://primer.style/view-components/lookbook/inspect/primer/open_project/sub_header/default/", "parameters": [ + { + "name": "collapsed_search", + "type": "Boolean", + "default": "`false`", + "description": "When true, the search bar starts collapsed as an icon button on all screen sizes. Clicking expands it." + }, { "name": "system_arguments", "type": "Hash", @@ -21672,6 +21678,11 @@ "description": "A button or custom content that will render on the left-hand side of the component, next to the filter input.\n\nTo render a button, call the `with_filter_button` method, which accepts the arguments accepted by {{#link_to_component}}Primer::Beta::Button{{/link_to_component}}.\n\nTo render custom content, call the `with_filter_component` method and pass a block that returns HTML.", "parameters": [] }, + { + "name": "quick_filters", + "description": "Quick filters shown in the left pane next to the search bar (0–5 items).\nHidden on mobile. Requires all_filters_button to be set when used.\nSupports ActionMenus, Buttons, IconButtons, SelectPanels, and SegmentedControls inside the block.", + "parameters": [] + }, { "name": "segmented_control", "description": null, @@ -21781,6 +21792,32 @@ ] } }, + { + "preview_path": "primer/open_project/sub_header/collapsed_search", + "name": "collapsed_search", + "snapshot": "false", + "skip_rules": { + "wont_fix": [ + "region" + ], + "will_fix": [ + "color-contrast" + ] + } + }, + { + "preview_path": "primer/open_project/sub_header/quick_filters", + "name": "quick_filters", + "snapshot": "false", + "skip_rules": { + "wont_fix": [ + "region" + ], + "will_fix": [ + "color-contrast" + ] + } + }, { "preview_path": "primer/open_project/sub_header/bottom_pane", "name": "bottom_pane", diff --git a/static/previews.json b/static/previews.json index 4e7a8f63e1..9affedf641 100644 --- a/static/previews.json +++ b/static/previews.json @@ -8182,6 +8182,32 @@ ] } }, + { + "preview_path": "primer/open_project/sub_header/collapsed_search", + "name": "collapsed_search", + "snapshot": "false", + "skip_rules": { + "wont_fix": [ + "region" + ], + "will_fix": [ + "color-contrast" + ] + } + }, + { + "preview_path": "primer/open_project/sub_header/quick_filters", + "name": "quick_filters", + "snapshot": "false", + "skip_rules": { + "wont_fix": [ + "region" + ], + "will_fix": [ + "color-contrast" + ] + } + }, { "preview_path": "primer/open_project/sub_header/bottom_pane", "name": "bottom_pane", From 347e0d1fac2a0d7e6457195818e8d9ad84eaca9a Mon Sep 17 00:00:00 2001 From: "openproject-primer-view-components[bot]" <249250993+openproject-primer-view-components[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 08:37:26 +0000 Subject: [PATCH 5/5] Generating component snapshots --- .../text_area/with_character_limit/light.png | Bin 9395 -> 9394 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/text_area/with_character_limit/light.png b/.playwright/screenshots/snapshots.test.ts-snapshots/primer/alpha/text_area/with_character_limit/light.png index cf9a99450839e3b665ff77da47f026b4f5e67632..4f9d2a69c901e70e0ea2d425ebb7247b05ee0e20 100644 GIT binary patch delta 5333 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