Skip to content

Commit 710cc93

Browse files
committed
[#3016] 💄 Desktop search filters - new design
1 parent eaf6816 commit 710cc93

File tree

19 files changed

+763
-150
lines changed

19 files changed

+763
-150
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
{% load i18n icon_tags button_tags form_tags %}
22

3-
<div>
4-
<fieldset class="filter" aria-label="{% trans "Filter" %}">
5-
{% button href="#" icon="expand_more" icon_position="after" extra_classes="filter__mobile filter--toggle" bordered=False text=field.label %}
6-
<legend class="filter__title">
7-
<span class="filter__legend-label">{{ field.label }}</span>
8-
</legend>
9-
<div class="filter__list">
10-
{% for option in field.field.choices %}
11-
{% choice_checkbox choice=option name=field.name data=field.data index=forloop.counter form_id=form_id %}
12-
{% endfor %}
3+
<!-- TODO change open class from 'show' to a fitting modifier such ass &--open. -->
4+
<div class="filter-dropdown filter__container">
5+
<div class="filter filter-dropdown__dropdown">
6+
<button type="button" class="button button--transparent button--borderless filter__title" aria-haspopup="listbox" aria-expanded="{{ initial_open|yesno:'true,false' }}">
7+
<span>
8+
{{ field.label }}
9+
<span class="filter__counter">({{field.field.choices|length}})</span>
10+
</span>
11+
{% icon icon="expand_more" icon_outlined=True %}
12+
</button>
13+
<div class="filter-dropdown__content filter__list {% if initial_open %}show{% endif %}" role="listbox" aria-hidden="{{ initial_open|yesno:'false,true' }}">
14+
<div class="filter-dropdown__menu">
15+
<ul class="filter-dropdown__list">
16+
{% for option in field.field.choices %}
17+
<li class="filter-dropdown__list-item">
18+
{% choice_checkbox as_tag=checkbox_as_tag choice=option name=field.name data=field.data index=forloop.counter form_id=form_id append_to_checkbox_id=append_to_checkbox_id %}
19+
</li>
20+
{% endfor %}
21+
</ul>
22+
</div>
1323
</div>
14-
</fieldset>
15-
<hr class="divider divider--tiny">
16-
</div>
24+
</div>
25+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{% load i18n icon_tags button_tags form_tags %}
2+
3+
<div class="filter-modal__initial">
4+
{% button icon="filter_alt" text=_("Filters") icon_outlined=True transparent=True extra_classes="show-modal" %}
5+
{% icon icon="check" icon_position="after" outlined=True %}
6+
<span class="sr-only">{% trans "Gekozen filters" %}</span>
7+
</div>
8+
<div class="filter-modal filter-modal__backdrop" id="filterModal">
9+
<div class="filter-modal--mobile">
10+
<div class="filter-modal__mobile-controls">
11+
{% button icon="close" text=_("Sluiten") hide_text=True icon_outlined=True transparent=True extra_classes="show-controls filter-modal__close" %}
12+
<div class="filter-modal__reset">
13+
{% button text=_("Wis alle?") icon_outlined=True transparent=True extra_classes="filter-modal__reset-button filter__reset" %}
14+
</div>
15+
</div>
16+
<p class="utrecht-paragraph filter-modal__heading">{% trans "Filters" %}</p>
17+
<div class="filter-modal__contents">
18+
{{ contents }}
19+
</div>
20+
</div>
21+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{% load i18n icon_tags button_tags form_tags filter_tags utils %}
2+
3+
4+
{# Mobile search filters - inside modal #}
5+
<div class="filters filters--mobile">
6+
{% render_filter_modal %}
7+
{% if search_filter_categories and search_form.categories.field.choices|length != 0 %}
8+
{% filter field=search_form.categories form_id=form_id open_filter_index=open_filter_index index=0 append_to_checkbox_id='mobile' %}
9+
{% endif %}
10+
{% if search_filter_tags and search_form.tags.field.choices|length != 0 %}
11+
{% filter field=search_form.tags form_id=form_id open_filter_index=open_filter_index index=1 append_to_checkbox_id='mobile' %}
12+
{% endif %}
13+
{% if search_filter_organizations and search_form.organizations.field.choices|length != 0 %}
14+
{% filter field=search_form.organizations form_id=form_id open_filter_index=open_filter_index index=2 append_to_checkbox_id='mobile' %}
15+
{% endif %}
16+
{% endrender_filter_modal %}
17+
</div>
18+
19+
{# Desktop search filters #}
20+
<div class="filters filters--desktop">
21+
<div class='filter__reset'>
22+
{% button transparent=True type='button' inline=True text=_('Wis alle?') %}
23+
</div>
24+
25+
{% if search_filter_categories and search_form.categories.field.choices|length != 0 %}
26+
{% filter field=search_form.categories form_id=form_id open_filter_index=open_filter_index index=0 checkbox_as_tag=True %}
27+
{% endif %}
28+
29+
{% if search_filter_tags and search_form.tags.field.choices|length != 0 %}
30+
{% filter field=search_form.tags form_id=form_id open_filter_index=open_filter_index index=1 checkbox_as_tag=True %}
31+
{% endif %}
32+
33+
{% if search_filter_organizations and search_form.organizations.field.choices|length != 0 %}
34+
{% filter field=search_form.organizations form_id=form_id open_filter_index=open_filter_index index=2 checkbox_as_tag=True %}
35+
{% endif %}
36+
</div>
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{% load l10n form_tags %}
22

3-
<div class="checkbox">
3+
<div class="checkbox {% if as_tag %}checkbox--tag{% endif %}">
44
<input
55
class="checkbox__input"
66
type="checkbox"
77
name="{{ name }}"
88
value="{{ choice.0|unlocalize }}"
9-
id="id_{{ name }}_{{ index }}"
9+
id="id_{{ name }}_{{ index }}{% if append_to_checkbox_id %}_{{append_to_checkbox_id}}{% endif %}"
1010
{% if form_id %}form="{{ form_id }}"{% endif %}
1111
{% if choice.0 in data or choice.0|add:0 in data %}checked="checked"{% endif %}
1212
/>
13-
<label class="checkbox__label" for="id_{{ name }}_{{ index }}">{{ choice.1 }}</label>
13+
<label class="checkbox__label" for="id_{{ name }}_{{ index }}{% if append_to_checkbox_id %}_{{append_to_checkbox_id}}{% endif %}">{{ choice.1 }}</label>
1414
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
from django import template
2+
from .helpers import create_content_wrapper
3+
4+
register = template.Library()
5+
6+
7+
@register.inclusion_tag("components/Filter/SearchFilters.html")
8+
def search_filters(
9+
search_form,
10+
search_filter_categories,
11+
search_filter_tags,
12+
search_filter_organizations,
13+
**kwargs
14+
):
15+
"""
16+
Renders the actions in a filterable table.
17+
18+
Usage:
19+
{% search_filters search_form form_id='form_id' search_filter_categories search_filter_tags search_filter_organizations %}
20+
21+
Available options:
22+
+ search_form: BaseForm | The form that contains the fields with choices.
23+
+ form_id: str | The id of the form.
24+
+ search_filter_categories: bool | The config bool that defines if the categorie choices are rendered.
25+
+ search_filter_tags: bool | The config bool that defines if the tag choices are rendered.
26+
+ search_filter_organizations: bool | The config bool that defines if the organization choices are rendered.
27+
"""
28+
29+
open_filter_index = 0
30+
31+
# Determine the initial open state based on active filters and their choices
32+
filters = [
33+
(search_filter_categories, len(search_form["categories"].field.choices), 0),
34+
(search_filter_tags, len(search_form["tags"].field.choices), 1),
35+
(
36+
search_filter_organizations,
37+
len(search_form["organizations"].field.choices),
38+
2,
39+
),
40+
]
41+
42+
# Set initial_open based on which filter is active and has choices
43+
for filter_active, choices_len, index in filters:
44+
if filter_active and choices_len != 0:
45+
open_filter_index = index
46+
break # Stop once we find the first active filter with choices
47+
48+
kwargs["search_form"] = search_form
49+
kwargs["search_filter_categories"] = search_filter_categories
50+
kwargs["search_filter_tags"] = search_filter_tags
51+
kwargs["search_filter_organizations"] = search_filter_organizations
52+
kwargs["open_filter_index"] = open_filter_index
53+
54+
return {**kwargs}
55+
56+
57+
@register.inclusion_tag("components/Filter/SearchFiltersMobile.html")
58+
def search_filters_mobile(
59+
search_form,
60+
search_filter_categories,
61+
search_filter_tags,
62+
search_filter_organizations,
63+
**kwargs
64+
):
65+
"""
66+
Renders the actions in a filterable table.
67+
68+
Usage:
69+
{% search_filters search_form form_id='form_id' search_filter_categories search_filter_tags search_filter_organizations %}
70+
71+
Available options:
72+
+ search_form: BaseForm | The form that contains the fields with choices.
73+
+ form_id: str | The id of the form.
74+
+ search_filter_categories: bool | The config bool that defines if the categorie choices are rendered.
75+
+ search_filter_tags: bool | The config bool that defines if the tag choices are rendered.
76+
+ search_filter_organizations: bool | The config bool that defines if the organization choices are rendered.
77+
"""
78+
79+
open_filter_index = 0
80+
81+
# Determine the initial open state based on active filters and their choices
82+
filters = [
83+
(search_filter_categories, len(search_form["categories"].field.choices), 0),
84+
(search_filter_tags, len(search_form["tags"].field.choices), 1),
85+
(
86+
search_filter_organizations,
87+
len(search_form["organizations"].field.choices),
88+
2,
89+
),
90+
]
91+
92+
# Set initial_open based on which filter is active and has choices
93+
for filter_active, choices_len, index in filters:
94+
if filter_active and choices_len != 0:
95+
open_filter_index = index
96+
break # Stop once we find the first active filter with choices
97+
98+
kwargs["search_form"] = search_form
99+
kwargs["search_filter_categories"] = search_filter_categories
100+
kwargs["search_filter_tags"] = search_filter_tags
101+
kwargs["search_filter_organizations"] = search_filter_organizations
102+
kwargs["open_filter_index"] = open_filter_index
103+
kwargs["append_to_checkbox_id"] = kwargs.get('append_to_checkbox_id', None)
104+
105+
return {**kwargs}
106+
107+
108+
@register.inclusion_tag("components/Filter/Filter.html")
109+
def filter(field, **kwargs):
110+
"""
111+
Renders the actions in a filterable table.
112+
113+
Usage:
114+
{% filter field=search_form.tags form_id=form_id open_filter_index=1 index=1 %}
115+
116+
Available options:
117+
+ field: Field | The field that needs to be rendered.
118+
+ form_id: str | The id of the form.
119+
+ open_filter_index: int | The index of the filter that should render open.
120+
+ index: int | The index of the current filter.
121+
+ checkbox_as_tag: bool | Boolean indicating if the checkbox should render as a tag.
122+
"""
123+
124+
kwargs["initial_open"] = kwargs["open_filter_index"] is kwargs["index"]
125+
checkbox_as_tag = kwargs.get('checkbox_as_tag', False)
126+
127+
return {"field": field, "checkbox_as_tag": checkbox_as_tag, **kwargs}
128+
129+
130+
@register.tag
131+
def render_filter_modal(parser, token):
132+
"""
133+
This is used to render in a grid.
134+
135+
Usage:
136+
{% render_filter_modal %}
137+
<div>Some content here</div>
138+
<div>Some content here too</div>
139+
{% endrender_filter_modal %}
140+
141+
Variables:
142+
- compact: bool | Whether to use compact mode (no gutters).
143+
144+
Extra context:
145+
- contents: string (HTML) | this is the context between the render_grid and endrender_grid tags
146+
"""
147+
return create_content_wrapper("render_filter_modal", "components/Filter/FilterModal.html")(
148+
parser, token
149+
)

src/open_inwoner/js/components/search/filter-mobile.js

-33
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const FILTER_MODALS = document.querySelectorAll('.filter-modal')
2+
3+
export class FilterModal {
4+
/**
5+
* Construct the FilterModal class.
6+
* @param {HTMLDivElement} node
7+
*/
8+
constructor(node) {
9+
this.node = node
10+
this.bindEvents()
11+
}
12+
13+
/**
14+
* Bind all event handlers.
15+
*/
16+
bindEvents() {
17+
// Open modal
18+
this.showModalButton?.addEventListener('click', this.openModal.bind(this))
19+
// Close modal
20+
this.closeButton?.addEventListener('click', this.closeModal.bind(this))
21+
// Close modal with keys.
22+
document.addEventListener('keydown', this.filterClosing.bind(this), false)
23+
}
24+
25+
get isModalVisible() {
26+
return this.node.classList.contains('filter-modal--show')
27+
}
28+
29+
get closeButton() {
30+
this.node.querySelector('.filter-modal__close')
31+
}
32+
33+
get showModalButton() {
34+
return document.querySelector('.show-modal')
35+
}
36+
37+
openModal() {
38+
this.node.classList.toggle('filter-modal--show', true)
39+
document.body.classList.toggle('body--noscroll', true)
40+
this.node.setAttribute('aria-expanded', true)
41+
this.node.setAttribute('aria-label', 'Sluiten Filters')
42+
}
43+
44+
closeModal() {
45+
this.node.classList.toggle('filter-modal--show', false)
46+
document.body.classList.toggle('body--noscroll', false)
47+
this.node.setAttribute('aria-expanded', false)
48+
this.node.setAttribute('aria-label', 'Filters')
49+
}
50+
51+
filterClosing(event) {
52+
if (
53+
event.type === 'keydown' &&
54+
event.key === 'Escape' &&
55+
this.isModalVisible
56+
) {
57+
this.closeModal() // Close the modal on Escape key press
58+
}
59+
}
60+
}
61+
62+
// Start!
63+
;[...FILTER_MODALS].forEach((node) => new FilterModal(node))

src/open_inwoner/js/components/search/filter-options.js

-7
This file was deleted.

0 commit comments

Comments
 (0)