wagtail / wagtail

A Django content management system focused on flexibility and user experience

Home Page:https://wagtail.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Minor UI Bug: Invisible Snippet Filter Text

KalobTaulien opened this issue · comments

Issue Summary

White text on white background (light mode only). However, other filter windows seem to work perfectly. Images below can explain the problem better than I can.

image
image
image

Here's the DOM in case there's something odd about this:
image

Steps to Reproduce

I've registered EmailAddress from Django Allauth. Code below:

from allauth.account.models import EmailAddress

from wagtail.admin.panels import FieldPanel
from wagtail.snippets.models import register_snippet
from wagtail.snippets.views.snippets import SnippetViewSet


@register_snippet
class EmailAddressSnippetViewSet(SnippetViewSet):
    model = EmailAddress

    list_display = ["user", "email", "verified"]
    list_filter = ["verified"]

    export_headings = {
        "user": "Username",
        "email_address": "Email",
        "verified": "Verified",
    }
    list_export = ["user", "email", "verified"]
    search_fields = ["user__username", "email"]


    panels = [
        FieldPanel("id", read_only=True),
        FieldPanel("user", widget=UserChooserWidget),
        FieldPanel("email"),
        FieldPanel("verified"),
        FieldPanel("primary"),
    ]

Technical details

  • Python 3.10
  • Django 5.0.2
  • Wagtail 6.0

Working on this

One CSS selector could fix this. Very minor.

Anyone can contribute to this. View our contributing guidelines, add a comment to the issue once you’re ready to start.

Trying to reproduce the issue,will revert back with more details.

image

I can confirm the issue exist.

I think perhaps it is caused by the color specified in the code below.

.tippy-box {
    background-color: var(--w-color-surface-tooltip);
    border-radius: 0.1875rem;
    color: var(--w-color-text-button);
    font-size: .75rem;
    font-weight: 500;
}

Thank you @KalobTaulien for a solid report! We set the color for the text of those widgets here:

color: theme('colors.text-context');

This only applies to three widget types though:

.w-field--checkbox_select_multiple,
.w-field--checkbox_select_multiple_with_disabled_options,
.w-field--radio_select

So I presume this filter is probably using a different widget from the ones we have special treatment for?

It uses Wagtail's BooleanRadioSelect (a subclass of RadioSelect):

class BooleanRadioSelect(forms.RadioSelect):

It is automatically applied from

@classmethod
def filter_for_lookup(cls, field, lookup_type):
filter_class, params = super().filter_for_lookup(field, lookup_type)
if filter_class == django_filters.ChoiceFilter:
params.setdefault("widget", forms.RadioSelect)
params.setdefault("empty_label", _("All"))
elif filter_class in [django_filters.DateFilter, django_filters.DateTimeFilter]:
params.setdefault("widget", AdminDateInput)
elif filter_class == django_filters.DateFromToRangeFilter:
params.setdefault("widget", DateRangePickerWidget)
elif filter_class == django_filters.BooleanFilter:
params.setdefault("widget", BooleanRadioSelect)
return filter_class, params

The widget's camel case class name is turned into snake_case in here

] = f"w-field--{ fieldtype(field) } w-field--{ widgettype(field) }"

Unfortunately, it only uses the final class itself and not any of the superclasses, so the element only gets the w-field--boolean_radio_select class and not w-field--radio_select. Only the latter has the corresponding CSS, hence this issue.

The solution is to either:

  • add w-field--boolean_radio_select to the CSS selectors.
  • or more robustly, update the class generation to also generate the classnames for the parent classes by inspecting the class's __mro__, so that the element gets both w-field--boolean_radio_select w-field--radio_select classes. This ensures any custom widgets that are subclasses of the widgets that have been styled by Wagtail will have Wagtail's styles by default, while still allowing customisations by developers using more specific selectors of the subclass + parent classes.

The page listing's has_child_pages filter does not have this issue as it uses the plain RadioSelect widget with custom choices:

has_child_pages = HasChildPagesFilter(
label=_("Has child pages"),
empty_label=_("Any"),
choices=[
("true", _("Yes")),
("false", _("No")),
],
widget=RadioSelect,
)

It should probably be refactored to reuse the BooleanRadioSelect widget to ensure consistency of the labels.

Thank you @laymonage, excellent. I’d recommend we do both solutions:

  1. Add w-field--boolean_radio_select to the list of selectors I’ve shared, to fix the issue at hand without too much reinvention for a 6.0.1 patch release
  2. Create a separate issue to refactor the class generation based on the whole inheritance tree for Wagtail 6.1. Possibly refactoring our current set of CSS selectors too.

Is anyone keen to work on either of those two? If not I’ll take on 1. and create the issue for 2.

For has_child_pages and BooleanRadioSelect, I see the "unset" choice is different – I suppose we could change the label of BooleanRadioSelect to say "Any" and then reuse it? Or keep them separate.

I would like to push for our use of w-field--{some_snake_cased_python_class} classnames to be phased out. They create a coupling between "how server-side form logic is implemented" and "how a given widget should be styled" that really shouldn't be there. See #9320 for a previous case where this has bitten us - in fact the MRO fix was suggested there too, but that wouldn't help for cases where two widgets need to be styled the same way but have very different inheritance chains on the Python side, such as a chooser that chooses items from an API rather than a local model and therefore needs to be an IntegerField rather than a ModelChoiceField. (This was also acknowledged on that PR discussion, but doesn't seem to have made it into the fix itself - even after that PR was merged I've still had to fake it with a classname = "w-field--model_choice_field" to get the right styling).

If a form widget needs a classname for its styling, it should set one explicitly (and give it a name relevant to the styling that needs to be achieved) rather than rely on an automagically-set classname based on the Python code.

@gasman how would that work when we apply custom styles to vanilla forms widgets?

@thibaudcolas If it's not possible purely with CSS selectors like input[type="radio"] (and I'm sure you wouldn't be asking if it was :-) ), we can define subclasses of the vanilla widgets with the classnames in place, and register them as overrides so that they get used as standard for forms inheriting from WagtailAdminModelForm. (AdminAutoHeightTextInput in particular is a good example of a subclass that adds a classname and not much else.)

We can probably create a similar mechanism for non-model forms if needed - although since those specify fields explicitly, it's probably not a big deal to say "here are Wagtail's widget classes, use them in your form definitions if you want them to be styled correctly".

Ahh! That sounds great, I forgot we had this way to register overrides.