From 919d6c98ee17ba14cb3b7e557777459a265c72c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Wed, 10 Jun 2026 17:51:07 +0200 Subject: [PATCH] feat: implement StringFilter composite component with 4x2 grid radios --- common/components/__init__.py | 2 ++ common/components/filters.py | 66 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/common/components/__init__.py b/common/components/__init__.py index 174f8db..44aa1f6 100644 --- a/common/components/__init__.py +++ b/common/components/__init__.py @@ -29,6 +29,7 @@ from common.components.filters import ( PlayEventFilterBar, PurchaseFilterBar, SessionFilterBar, + StringFilter, ) from common.components.primitives import ( H1, @@ -128,4 +129,5 @@ __all__ = [ "DeviceFilterBar", "PlatformFilterBar", "PlayEventFilterBar", + "StringFilter", ] diff --git a/common/components/filters.py b/common/components/filters.py index 6b4dff7..0630292 100644 --- a/common/components/filters.py +++ b/common/components/filters.py @@ -1471,3 +1471,69 @@ def PlayEventFilterBar( ), ] return _filter_bar(fields, filter_json, preset_list_url, preset_save_url) + + +def StringFilter( + input_name_prefix: str, + value: str = "", + modifier: str = "EQUALS", + placeholder: str = "", +) -> SafeText: + """Renders a string filter with 8 modifier radio options and a text input.""" + from common.criteria import Modifier + + if modifier not in [m.value for m in Modifier.for_strings()]: + modifier = "EQUALS" + + options = [ + ("EQUALS", "is"), + ("NOT_EQUALS", "is not"), + ("INCLUDES", "includes"), + ("EXCLUDES", "excludes"), + ("MATCHES_REGEX", "matches regex"), + ("NOT_MATCHES_REGEX", "not matches regex"), + ("IS_NULL", "is null"), + ("NOT_NULL", "is not null"), + ] + + # Grid of Radios using standard Radio primitives + radio_buttons = [ + Radio( + name=f"{input_name_prefix}-modifier", + label=lbl, + checked=(modifier == mod_val), + value=mod_val, + attributes=[ + ("data-string-modifier-radio", ""), + ("onclick", "toggleStringFilterInput(this)"), + ] + ) + for mod_val, lbl in options + ] + + input_disabled = modifier in ("IS_NULL", "NOT_NULL") + + input_attrs = [ + ("type", "text"), + ("name", input_name_prefix), + ("value", value if not input_disabled else ""), + ("placeholder", placeholder), + ( + "class", + "w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body transition-all " + + ("opacity-50 cursor-not-allowed" if input_disabled else ""), + ), + ] + if input_disabled: + input_attrs.append(("disabled", "true")) + + return Div( + attributes=[("class", "flex flex-col gap-2")], + children=[ + Div( + attributes=[("class", "grid grid-cols-2 sm:grid-cols-4 gap-2 py-1")], + children=radio_buttons, + ), + Input(attributes=input_attrs), + ], + )