Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat hide clear button #174

Merged
merged 15 commits into from
Sep 10, 2024
Merged
5 changes: 5 additions & 0 deletions .changeset/nice-adults-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@openproject/primer-view-components': minor
---

Toggle the visibility of the clear button on the SubHeader component's filter input based on its content.
17 changes: 16 additions & 1 deletion app/components/primer/open_project/sub_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,25 @@ class SubHeader < Primer::Component
system_arguments[:data] ||= {}
system_arguments[:data][:target]= "sub-header.filterInput"

system_arguments[:show_clear_button] = true if system_arguments[:show_clear_button].nil?

if system_arguments[:show_clear_button]
system_arguments[:data] = merge_data(
system_arguments,
{
data: {
action: <<~JS
input:sub-header#toggleFilterInputClearButton
focus:sub-header#toggleFilterInputClearButton
JS
}
}
)
end

@mobile_filter_trigger = Primer::Beta::IconButton.new(icon: system_arguments[:leading_visual][:icon],
display: [:inline_flex, :none],
aria: {label: label },
aria: { label: label },
"data-action": "click:sub-header#expandFilterInput",
"data-targets": HIDDEN_FILTER_TARGET_SELECTOR)

Expand Down
51 changes: 50 additions & 1 deletion app/components/primer/open_project/sub_header_element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@ import {controller, target, targets} from '@github/catalyst'

@controller
class SubHeaderElement extends HTMLElement {
@target filterInput: HTMLElement
@target filterInput: HTMLInputElement
@targets hiddenItemsOnExpandedFilter: HTMLElement[]
@targets shownItemsOnExpandedFilter: HTMLElement[]

clearFilterButton: HTMLButtonElement | null
clearButtonWrapper: HTMLElement | null

connectedCallback() {
this.setupFilterInputClearButton()
}

toggleFilterInputClearButton() {
if (!(this.clearButtonWrapper && this.clearFilterButton)) {
return
}
if (this.filterInput.value.length > 0) {
this.clearFilterButton.classList.remove('d-none')
} else {
this.clearFilterButton.classList.add('d-none')
}
}

expandFilterInput() {
for (const item of this.hiddenItemsOnExpandedFilter) {
item.classList.add('d-none')
Expand All @@ -31,6 +49,37 @@ class SubHeaderElement extends HTMLElement {

this.classList.remove('SubHeader--expandedSearch')
}

setupFilterInputClearButton() {
this.waitForCondition(
() => Boolean(this.filterInput),
() => {
this.clearFilterButton = this.querySelector('button.FormControl-input-trailingAction') as HTMLButtonElement
this.clearButtonWrapper = this.clearFilterButton.closest('.FormControl-input-wrap') as HTMLElement
dombesz marked this conversation as resolved.
Show resolved Hide resolved

if (this.clearFilterButton) {
this.toggleFilterInputClearButton()
}
},
)
}

// Waits for condition to return true. If it returns false initially, this function creates a
// MutationObserver that calls body() whenever the contents of the component change.
waitForCondition(condition: () => boolean, body: () => void) {
if (condition()) {
body()
} else {
const mutationObserver = new MutationObserver(() => {
if (condition()) {
body()
mutationObserver.disconnect()
}
})

mutationObserver.observe(this, {childList: true, subtree: true})
}
}
}

declare global {
Expand Down
17 changes: 15 additions & 2 deletions previews/primer/open_project/sub_header_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,23 @@ class SubHeaderPreview < ViewComponent::Preview
# @param show_filter_input toggle
# @param show_filter_button toggle
# @param show_action_button toggle
# @param show_clear_button toggle
# @param text text
def playground(show_filter_input: true, show_filter_button: true, show_action_button: true, text: nil)
# @param value text
def playground(
show_filter_input: true,
show_clear_button: true,
show_filter_button: true,
show_action_button: true,
text: nil,
value: nil
)
render(Primer::OpenProject::SubHeader.new) do |component|
component.with_filter_input(name: "filter", label: "Filter") if show_filter_input
component.with_filter_input(
name: "filter",
label: "Filter",
show_clear_button: show_clear_button,
value: value) if show_filter_input
component.with_filter_button do |button|
button.with_trailing_visual_counter(count: "15")
"Filter"
Expand Down
39 changes: 39 additions & 0 deletions test/components/primer/open_project/sub_header_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,43 @@ def test_renders_a_custom_filter_button
assert_selector(".SubHeader .MyCustomButton")
assert_selector(".SubHeader .SubHeader-bottomPane .ABottomPane")
end

def test_renders_a_clear_button_when_show_clear_button_is_set
render_inline(Primer::OpenProject::SubHeader.new) do |component|
component.with_filter_input(
name: "filter",
label: "Filter",
show_clear_button: true,
value: "value is set"
)
end

assert_selector(".SubHeader")
assert_selector(
".SubHeader-filterInput"\
"[data-action=\"input:sub-header#toggleFilterInputClearButton\n"\
"focus:sub-header#toggleFilterInputClearButton\n\"]"
)
assert_selector(".FormControl-input-trailingAction[data-action=\"click:primer-text-field#clearContents\"]")
end

def test_does_not_render_input_events_when_show_clear_button_is_not_set
render_inline(Primer::OpenProject::SubHeader.new) do |component|
component.with_filter_input(
name: "filter",
label: "Filter",
show_clear_button: false,
value: "value is set"
)
end

assert_selector(".SubHeader")
assert_selector(".SubHeader-filterInput")
assert_no_selector(
".SubHeader-filterInput"\
"[data-action=\"input:sub-header#toggleFilterInputClearButton\n"\
"focus:sub-header#toggleFilterInputClearButton\n\"]"
)
assert_no_selector(".FormControl-input-trailingAction[data-action=\"click:primer-text-field#clearContents\"]")
end
end
27 changes: 27 additions & 0 deletions test/system/open_project/sub_header_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,31 @@ def test_renders_component

assert_selector(".SubHeader")
end

def render_clear_button_with_an_initial_value
visit_preview(:playground, show_clear_button: true, value: "value")

assert_selector(".FormControl-input-wrap--trailingAction")
assert_selector("button.FormControl-input-trailingAction")
end

def test_clear_button_functionality
visit_preview(:playground, show_clear_button: true)
# no clear button with empty value
assert_no_selector("button.FormControl-input-trailingAction")

fill_in "filter", with: "value"

# Clear the field with the "x" button
find(".FormControl-input-trailingAction").click
assert_no_selector("button.FormControl-input-trailingAction")

fill_in "filter", with: "value"
assert_selector("button.FormControl-input-trailingAction")

# Clear the field with backspace
filter_field = find_field "filter"
"value".length.times { filter_field.send_keys [:backspace] }
assert_no_selector("button.FormControl-input-trailingAction")
end
end
Loading