Turn checkbox filters into toggle switch. wp/74380

This commit is contained in:
David F
2026-05-19 14:59:09 +02:00
committed by David.
parent 683bda80b4
commit 88a29a0aff
8 changed files with 152 additions and 27 deletions
+11 -19
View File
@@ -30,27 +30,19 @@
class Filters::Inputs::BooleanForm < Filters::Inputs::BaseFilterForm
def add_operand(group)
group.check_box(
group.segmented_control(
name: "#{@filter.name}_value",
label: @filter.human_name,
visually_hide_label: true,
name: "v-#{@filter.class.key}",
value: "t",
unchecked_value: "f",
checked: @filter.values.first == "t"
value: @filter.values.first,
items: [
{ value: "f", label: I18n.t("general_text_No") },
{ value: "t", label: I18n.t("general_text_Yes") }
],
wrapper_data_attributes: {
"filter--filters-form-target": "filterValueContainer",
"filter-name": @filter.name
}
)
end
protected
def operand_input_id
"v-#{@filter.class.key}"
end
private
def filter_row_arguments
super.tap do |args|
args[:data][:"filter--filters-form-target"] = "filter filterValueContainer"
end
end
end
@@ -503,6 +503,12 @@ export default class FiltersFormController extends Controller {
return this.parseDateFilterValue(valueContainer, filterName);
}
const hiddenField = valueContainer.querySelector<HTMLInputElement>('input[type="hidden"]');
if (hiddenField) {
return hiddenField.value ? [hiddenField.value] : null;
}
const value = this.findTargetByName(filterName, this.simpleValueTargets)?.value;
if (value && value.length > 0) {
@@ -0,0 +1,51 @@
/*
* -- copyright
* OpenProject is an open source project management software.
* Copyright (C) the OpenProject GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 3.
*
* OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
* Copyright (C) 2006-2013 Jean-Philippe Lang
* Copyright (C) 2010-2013 the ChiliProject Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* See COPYRIGHT and LICENSE files for more details.
* ++
*
*/
import { Controller } from '@hotwired/stimulus';
export default class SegmentedControlController extends Controller {
static targets = ['field'];
declare readonly fieldTarget:HTMLInputElement;
// Reacts to Primer's itemActivated event (bubbled from segmented-control#select),
// which has already updated aria-current visually. Sets the backing hidden field
// value and bubbles a change event for filter--filters-form auto-submit.
activate(event:CustomEvent<{ item:HTMLButtonElement }>) {
const button = event.detail.item;
const value = button.dataset.value;
if (value !== undefined) {
this.fieldTarget.value = value;
this.fieldTarget.dispatchEvent(new Event('change', { bubbles: true }));
}
}
}
@@ -36,6 +36,10 @@ module Primer
add_input AutocompleterInput.new(builder:, form:, **decorate_options(**), &)
end
def segmented_control(**, &)
add_input SegmentedControlInput.new(builder:, form:, **decorate_options(**), &)
end
def block_note_editor(**, &)
add_input BlockNoteEditorInput.new(builder:, form:, **decorate_options(**), &)
end
@@ -0,0 +1,37 @@
# frozen_string_literal: true
module Primer
module OpenProject
module Forms
module Dsl
class SegmentedControlInput < Primer::Forms::Dsl::Input
Item = Data.define(:value, :label)
attr_reader :name, :label, :current_value, :items, :wrapper_data_attributes
def initialize(name:, label:, value:, items:, wrapper_data_attributes: {}, **system_arguments)
@name = name
@label = label
@items = items.map { |item| Item.new(value: item[:value], label: item[:label]) }
@current_value = value.presence || @items.first&.value
@wrapper_data_attributes = wrapper_data_attributes
super(**system_arguments)
end
def to_component
SegmentedControl.new(input: self)
end
def type
:segmented_control
end
def focusable?
true
end
end
end
end
end
end
@@ -0,0 +1,18 @@
<%= render(FormControl.new(input: @input, data: @input.wrapper_data_attributes)) do %>
<div data-controller="filter--segmented-control"
data-action="itemActivated->filter--segmented-control#activate">
<%= content_tag(:input, nil,
type: "hidden",
id: builder.field_id(@input.name),
name: builder.field_name(@input.name),
value: @input.current_value,
data: { "filter--segmented-control-target": "field" }) %>
<%= render(Primer::Alpha::SegmentedControl.new("aria-label": @input.label)) do |control| %>
<% @input.items.each do |item| %>
<% control.with_item(label: item.label,
selected: item.value == @input.current_value,
data: { value: item.value }) %>
<% end %>
<% end %>
</div>
<% end %>
@@ -0,0 +1,19 @@
# frozen_string_literal: true
module Primer
module OpenProject
module Forms
# :nodoc:
class SegmentedControl < Primer::Forms::BaseComponent
prepend WrappedInput
delegate :builder, :form, to: :@input
def initialize(input:)
super()
@input = input
end
end
end
end
end
+6 -8
View File
@@ -134,16 +134,14 @@ module Components
def set_toggle_filter(values)
should_active = values.first == "yes"
checkbox = page.find('input[type="checkbox"]')
is_active = checkbox.checked?
label = should_active ? I18n.t("general_text_Yes") : I18n.t("general_text_No")
hidden = page.find('input[type="hidden"]', visible: :all)
is_active = hidden.value == "t"
checkbox.click if should_active != is_active
click_button(label, exact: true) unless should_active == is_active
if should_active
expect(page).to have_field(type: :checkbox, checked: true)
else
expect(page).to have_field(type: :checkbox, checked: false)
end
expected_value = should_active ? "t" : "f"
expect(page).to have_field(hidden["name"], with: expected_value, type: :hidden)
end
def set_name_and_identifier_filter(values, send_keys: false)