Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stupid-signs-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@openproject/primer-view-components': minor
---

Extend SubHeader to support QuickFilters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 11 additions & 8 deletions app/components/primer/open_project/sub_header.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
<div class="SubHeader-leftPane">
<%= 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? %>
<%= 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 %>

Expand All @@ -22,11 +25,11 @@
<% end %>
</div>

<div class="SubHeader-middlePane" data-targets="<%= HIDDEN_FILTER_TARGET_SELECTOR %>">
<div class="SubHeader-middlePane SubHeader-hiddenOnExpand">
<%= text %>
</div>

<div class="SubHeader-rightPane" data-targets="<%= HIDDEN_FILTER_TARGET_SELECTOR %>">
<div class="SubHeader-rightPane SubHeader-hiddenOnExpand">
<% actions.each do |action| %>
<%= action %>
<% end %>
Expand Down
21 changes: 14 additions & 7 deletions app/components/primer/open_project/sub_header.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@
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);
}

.SubHeader--expandedSearch {
grid-template-areas: "left left left" "bottom bottom bottom";
}

.SubHeader-rightPane {
grid-area: right;
display: flex;
Expand All @@ -22,6 +18,7 @@
.SubHeader-middlePane {
grid-area: middle;
text-align: center;
white-space: nowrap;
}

.SubHeader-bottomPane {
Expand All @@ -32,6 +29,8 @@
grid-area: left;
display: flex;
align-items: center;
flex-wrap: wrap;
row-gap: var(--base-size-16);
width: 100%;

/* Since the container is not full width (due to the grid around it)
Expand All @@ -54,12 +53,20 @@
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;
}

.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;
Expand All @@ -76,6 +83,6 @@
}

.SubHeader-middlePane:has(> *) {
margin-top: var(--stack-gap-normal);
margin-top: var(--base-size-16);
}
}
71 changes: 46 additions & 25 deletions app/components/primer/open_project/sub_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -165,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
Expand All @@ -182,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)
}
Expand All @@ -195,16 +213,18 @@ 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(**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 })

Expand All @@ -215,21 +235,22 @@ def initialize(**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

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
Expand Down
15 changes: 5 additions & 10 deletions app/components/primer/open_project/sub_header_element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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')
}

Expand Down
24 changes: 23 additions & 1 deletion previews/primer/open_project/sub_header_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ 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(
show_filter_input: true,
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",
Expand Down Expand Up @@ -98,6 +100,26 @@ 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 QuickFilters
def quick_filters
render_with_template(locals: {})
end

# @label With a custom area below
def bottom_pane
render_with_template(locals: {})
Expand Down
Original file line number Diff line number Diff line change
@@ -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 %>
6 changes: 6 additions & 0 deletions static/arguments.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion static/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading