From b49b5f1cc3516e6633858c9573107f77c1f76e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Fri, 19 Jun 2026 18:35:28 +0200 Subject: [PATCH] Make disabled SearchSelect read as one element MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The disabled widget showed two clashing surfaces in dark mode: the wrapper (faded via has-[:disabled]) plus the inner search input, which picked up the global disabled-input fill from common/input.css (`form input:disabled { background: neutral-secondary-strong }`). That rule is unlayered, so it beat any utility override on the input. Exclude the SearchSelect's inner search box from that global rule (`:not([data-search-select-search])`) so it stays transparent — the wrapper is then the single faded surface. Standalone inputs (e.g. the Name field) keep their distinct disabled surface, unchanged. e2e: assert the disabled inner input computes transparent background (one element), alongside the existing wrapper-opacity check. Co-Authored-By: Claude Opus 4.8 --- common/components/search_select.py | 7 +++++-- common/input.css | 7 +++++-- e2e/test_widgets_e2e.py | 4 ++++ games/static/base.css | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/common/components/search_select.py b/common/components/search_select.py index 450d552..a14dac3 100644 --- a/common/components/search_select.py +++ b/common/components/search_select.py @@ -13,8 +13,11 @@ This module imports only from ``common.components`` — it has no Django-forms o ``
``, which has no ``disabled`` state of its own. To disable it, set ``disabled`` on the inner search ```` (``[data-search-select-search]``); the wrapper then greys itself via the ``has-[:disabled]:`` utilities in -``_CONTAINER_CLASS``. Callers toggle only the control's ``disabled`` — never -styles. (See ``ts/add_purchase.ts`` gating ``related_game`` on the type field.) +``_CONTAINER_CLASS``. The inner input is excluded from the global +disabled-input surface (``common/input.css``) so it stays transparent — the +widget reads as one faded element, not a nested box. Callers toggle only the +control's ``disabled`` — never styles. (See ``ts/add_purchase.ts`` gating +``related_game`` on the type field.) Option sourcing follows two axes. *Population*: options are either rendered inline up front (``options=``, no ``search_url``) or fetched from ``search_url``. diff --git a/common/input.css b/common/input.css index a59fd09..8e2fa5d 100644 --- a/common/input.css +++ b/common/input.css @@ -127,11 +127,14 @@ } } -form input:disabled, +/* Standalone form controls get a distinct disabled surface. The SearchSelect's + inner search box is excluded: it's a composite widget that owns its disabled + look on the wrapper (has-[:disabled] in _CONTAINER_CLASS), so painting the + inner input here would render a nested box inside the wrapper. */ +form input:disabled:not([data-search-select-search]), select:disabled, textarea:disabled { @apply cursor-not-allowed bg-neutral-secondary-strong text-fg-disabled; - } .errorlist { diff --git a/e2e/test_widgets_e2e.py b/e2e/test_widgets_e2e.py index 2cf1925..7c9dc0d 100644 --- a/e2e/test_widgets_e2e.py +++ b/e2e/test_widgets_e2e.py @@ -174,6 +174,10 @@ def test_add_purchase_type_game_disables_related_game_search( expect(search).to_be_disabled() # The component greys itself via has-[:disabled] when the input is disabled. assert wrapper.evaluate("el => getComputedStyle(el).opacity") == "0.5" + # The disabled inner input stays transparent (excluded from the global + # disabled-input surface) so the widget reads as one element, not a nested + # box. transparent is mode-independent, so this holds in light and dark. + assert search.evaluate("el => getComputedStyle(el).backgroundColor") == "rgba(0, 0, 0, 0)" page.select_option("#id_type", "dlc") expect(search).to_be_enabled() diff --git a/games/static/base.css b/games/static/base.css index e036247..aa346d1 100644 --- a/games/static/base.css +++ b/games/static/base.css @@ -4400,7 +4400,7 @@ border-left-color: var(--color-slate-500); } } -form input:disabled, select:disabled, textarea:disabled { +form input:disabled:not([data-search-select-search]), select:disabled, textarea:disabled { cursor: not-allowed; background-color: var(--color-neutral-secondary-strong); color: var(--color-fg-disabled);