Skip to content

Commit 8c4f510

Browse files
committed
[#3081] modal selects radio plan
1 parent be84605 commit 8c4f510

File tree

8 files changed

+81
-27
lines changed

8 files changed

+81
-27
lines changed

src/open_inwoner/js/components/plan-preview/index.js

+43-2
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,21 @@ export class PlanPreview {
3030

3131
// Find the corresponding radio input
3232
let radioInput = null
33+
let radioLabel = null
3334

3435
// First structure (original page)
3536
const templateRow = this.node.closest('.plan-template__row')
3637
if (templateRow) {
3738
radioInput = templateRow.querySelector('.radio__input')
39+
radioLabel = templateRow.querySelector('.radio__label')
3840
}
3941

4042
// Second structure (choice-list page)
4143
if (!radioInput) {
4244
const choiceListItem = this.node.closest('.choice-list__item')
4345
if (choiceListItem) {
4446
radioInput = choiceListItem.querySelector('.choice-list__radio')
47+
radioLabel = choiceListItem.querySelector('.choice-list__label')
4548
}
4649
}
4750

@@ -51,6 +54,18 @@ export class PlanPreview {
5154
// As a fallback, try to find the radio by its ID
5255
if (!radioInput && templateId) {
5356
radioInput = document.getElementById(`id_template_${templateId}`)
57+
if (radioInput) {
58+
// Try to find the associated label
59+
radioLabel = document.querySelector(
60+
`label[for="id_template_${templateId}"]`
61+
)
62+
if (!radioLabel) {
63+
// If no explicit label found, look for parent or ancestor label
64+
radioLabel =
65+
radioInput.closest('label') ||
66+
radioInput.parentElement.querySelector('label')
67+
}
68+
}
5469
}
5570

5671
// Find all close buttons in the modal
@@ -67,6 +82,9 @@ export class PlanPreview {
6782
// Select the radio input
6883
radioInput.checked = true
6984

85+
// Set focus to the radio input to trigger CSS focus styles
86+
radioInput.focus()
87+
7088
// If using the choice-list structure, add the 'selected' class to the parent list item
7189
const choiceListItem = radioInput.closest('.choice-list__item')
7290
if (choiceListItem) {
@@ -81,15 +99,38 @@ export class PlanPreview {
8199
// Trigger change event to ensure any listeners know the radio was changed
82100
const changeEvent = new Event('change', { bubbles: true })
83101
radioInput.dispatchEvent(changeEvent)
102+
103+
// Add a small delay before setting focus to ensure DOM updates are processed
104+
setTimeout(() => {
105+
// For the first HTML variant, ensure focus affects the label for visual feedback
106+
if (radioInput && radioLabel && templateRow) {
107+
// First try to focus the label if possible (better for accessibility)
108+
if (radioLabel.getAttribute('for') === radioInput.id) {
109+
radioLabel.focus()
110+
} else {
111+
// Otherwise focus the input itself
112+
radioInput.focus()
113+
}
114+
}
115+
}, 50)
84116
},
85117
{ once: true }
86118
)
87119
})
88120
}
89121

90-
// Set the modal-closed callback to focus on the element that opened it
122+
// Set the modal-closed callback to focus on the selected radio or label
91123
modal.setModalClosedCallback(() => {
92-
if (modal.openedBy) {
124+
if (radioInput && radioInput.checked) {
125+
// Try to focus the label first (if it exists and is properly linked)
126+
if (radioLabel && radioLabel.getAttribute('for') === radioInput.id) {
127+
radioLabel.focus()
128+
} else {
129+
// Fall back to focusing the input itself
130+
radioInput.focus()
131+
}
132+
} else if (modal.openedBy) {
133+
// If no radio was selected, return focus to the element that opened the modal
93134
modal.openedBy.focus()
94135
}
95136
})

src/open_inwoner/scss/components/PlanTemplate/PlanTemplate.scss

+10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
.plan-template {
2+
scroll-margin-top: 122px;
3+
4+
&__table {
5+
display: grid;
6+
grid-template-columns: 1fr 1fr 16px;
7+
gap: var(--spacing-small);
8+
border-bottom: 1px solid var(--color-gray-light);
9+
padding: var(--spacing-medium);
10+
}
11+
212
&__options {
313
.choice-list {
414
display: grid;

src/open_inwoner/search/results.py

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from collections import OrderedDict
22
from dataclasses import dataclass
3+
from operator import attrgetter
34
from typing import Type
45

56
from django.db import models
@@ -80,6 +81,12 @@ def total_buckets(self) -> list[FacetBucket]:
8081
def choices(self) -> list:
8182
return [(b.slug, b.label) for b in self.buckets]
8283

84+
def total_choices(self) -> list:
85+
return [
86+
(b.slug, b.label)
87+
for b in sorted(self.total_buckets, key=attrgetter("slug"))
88+
]
89+
8390

8491
@dataclass()
8592
class ProductSearchResult:

src/open_inwoner/search/tests/test_page.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ def test_basic_search(self):
233233
# search to find both products
234234
page.goto(self.live_reverse("search:search", params={"query": "summary"}))
235235
page.wait_for_url(self.live_reverse("search:search", star=True))
236-
expect(page.locator(".search-results__item")).to_have_count(2)
236+
expect(page.locator(".card")).to_have_count(2)
237237

238238
def test_search_form_delegates_copy_query_value(self):
239239
context = self.browser.new_context()
@@ -273,11 +273,11 @@ def _do_search(query, form_id, open_menu):
273273

274274
# search from this page
275275
_do_search("summary", form_id, open_menu)
276-
expect(page.locator(".search-results__item")).to_have_count(2)
276+
expect(page.locator(".card")).to_have_count(2)
277277

278278
# perform another search from the search results page
279279
_do_search("other", form_id, open_menu)
280-
expect(page.locator(".search-results__item")).to_have_count(1)
280+
expect(page.locator(".card")).to_have_count(1)
281281

282282
page.close()
283283
context.close()
@@ -289,7 +289,7 @@ def test_search_with_filters(self):
289289
# search to find both products
290290
page.goto(self.live_reverse("search:search", params={"query": "summary"}))
291291
page.wait_for_url(self.live_reverse("search:search", star=True))
292-
expect(page.locator(".search-results__item")).to_have_count(2)
292+
expect(page.locator(".card")).to_have_count(2)
293293

294294
# check if we see all checkboxes
295295
for facet in FacetChoices.values:

src/open_inwoner/search/views.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def search(self, form):
115115
# update form fields with choices
116116
for facet in results.facets:
117117
if facet.name in form.fields:
118-
form.fields[facet.name].choices = facet.choices()
118+
form.fields[facet.name].choices = facet.total_choices()
119119

120120
# paginate
121121
paginator_dict = self.paginate_with_context(results.results)

src/open_inwoner/templates/pages/plans/choose-template.html

+1-3
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@ <h1 class="utrecht-heading-1">{% trans "Kies een voorstel" %}</h1>
1414
<div class="plan-template">
1515
<div class="plan-template__header">
1616
<span class="plan-tag">{% trans "Voorgesteld" %}</span>
17-
<div class="plan-template__row choice-list--single">
18-
<div class="label radio">
17+
<div class="plan-template__header-nav">
1918
{% button href="collaborate:plan_create_no_template" text=_("Zelf in te vullen samenwerking") primary=True transparent=True extra_classes="link--bold" icon="insert_drive_file" icon_outlined=True %}
20-
</div>
2119
</div>
2220
</div>
2321

src/open_inwoner/templates/pages/plans/create-no-template.html

+10-9
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
<div class="plan-create">
66
<div class="plan-template__header">
77
<h1 class="utrecht-heading-1">{% trans "Start nieuwe samenwerking" %}</h1>
8-
9-
<div class="plan-template__row choice-list--single">
10-
<div class="label radio">
11-
{% button href="#plan-templates" text=_("Gebruik sjabloon") primary=True transparent=True extra_classes="link--bold" icon="insert_drive_file" icon_outlined=True %}
12-
</div>
8+
<div class="plan-template__header-nav">
9+
{% button href="#plan-templates" text=_("Gebruik sjabloon") primary=True transparent=True extra_classes="link--bold" icon="insert_drive_file" icon_outlined=True %}
10+
{# TODO: Change scrollbutton into "href="collaborate:plan_choose_template" #}
1311
</div>
1412
</div>
1513

@@ -28,14 +26,14 @@ <h1 class="utrecht-heading-1">{% trans "Start nieuwe samenwerking" %}</h1>
2826

2927
<div class="">
3028
<div class="plan-template" id="plan-templates">
31-
<div class="label plan-template__row radio">
29+
<div class="label plan-template__row plan-template__table radio">
3230
<div>
3331
<input class="radio__input" type="radio" name="template" value="" id="id_template_0" {% if not form.data.template %}checked{% endif %}>
3432
<label class="radio__label" for="id_template_0">Geen template</label>
3533
</div>
3634
</div>
3735
{% for tpl_id, plan_template in form.template.field.choices %}
38-
<div class="label plan-template__row radio">
36+
<div class="label plan-template__row plan-template__table radio">
3937
<div>
4038
<input class="radio__input" type="radio" name="template" value="{{ plan_template.id }}" id="id_template_{{ plan_template.id }}"
4139
{% if tpl_id == form.data.template %}checked{% endif %}>
@@ -45,7 +43,7 @@ <h1 class="utrecht-heading-1">{% trans "Start nieuwe samenwerking" %}</h1>
4543
{{ plan_template.goal|truncatewords:8 }}
4644
</div>
4745
<div class="plan-template__icon">
48-
<button class="show-preview" type="button" data-id="template-{{ plan_template.id }}">
46+
<button class="show-preview" title="" type="button" data-id="template-{{ plan_template.id }}">
4947
{% icon icon="visibility" outlined=True %}
5048
</button>
5149
</div>
@@ -59,7 +57,10 @@ <h2 class="utrecht-heading-2 modal__title" id="modal__title">{% trans "Kies een
5957
</header>
6058
<div class="modal__text" id="modal__text">{{ plan_template.string_preview }}</div>
6159
<div class="modal__actions modal__actions--align-right" id="modal__actions">
62-
<button class="button modal__button modal__close button--primary button-icon--primary" type="button" aria-label="Sluit"><span class="inner-text">Sluiten </span></button>
60+
<div class="form__actions form__actions--fullwidth">
61+
{% button text=_("Gebruik dit voorstel") primary=True type="button" extra_classes="modal__close" %}
62+
{% button href="collaborate:plan_list" icon="west" text=_("Terug naar Mijn samenwerkingen") icon_outlined=True transparent=True %}
63+
</div>
6364
</div>
6465
</div>
6566
</div>

src/open_inwoner/templates/pages/plans/create-with-template.html

+5-8
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,18 @@
66
<div class="plan-template__header">
77
<h1>{{ object.title }}
88
{# remove once functional object exists #}
9-
[[ Title of chosen plan]] </h1>
10-
11-
<div class="plan-template__row choice-list--single">
12-
<div class="label radio">
13-
{% button href="collaborate:plan_choose_template" text=_("Gebruik sjabloon") primary=True transparent=True extra_classes="link--bold" icon="insert_drive_file" icon_outlined=True %}
14-
</div>
9+
[[ Title of chosen plan]] </h1>
10+
<div class="plan-template__header-nav">
11+
{% button href="collaborate:plan_create_no_template" text=_("Zelf in te vullen samenwerking") primary=True transparent=True extra_classes="link--bold" icon="insert_drive_file" icon_outlined=True %}
1512
</div>
1613
</div>
1714

1815
{% render_grid %}
1916
{% render_column start=3 span=7 %}
2017
{% if request.user.get_active_contacts %}
2118
{% render_form id="plan-form" form=form method="POST" %}
22-
{# TODO: May need different form, based on chosen template from previous step in flow#}
23-
{# {% render_form id="plan-form...?" form=form method="POST" %}#}
19+
{# TODO: May need different form, based on chosen template from previous step in flow #}
20+
{# {% render_form id="plan-form...?" form=form method="POST" %} #}
2421
{% csrf_token %}
2522
{% date_field form.end_date icon="today" icon_position="after" %}
2623
{% checkboxes form.plan_contacts %}

0 commit comments

Comments
 (0)