Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#2253] Add new modal with buttons #1009

Merged
merged 2 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
</div>
<div class="dropdown__item">
{% get_action_delete_url action=action plan=plan as action_url %}
{% render_form form=form method="POST" form_action=action_url extra_classes="confirm" spaceless=True data_confirm_title=_("Weet je het zeker dat je deze actie wilt verwijderen?") data_confirm_cancel=_("Nee") data_confirm_default=_("Ja") %}
{% render_form form=form method="POST" form_action=action_url extra_classes="confirm" spaceless=True data_confirm_title=_("Weet je het zeker dat je deze actie wilt verwijderen?") data_confirm_cancel=_("Nee, annuleren") data_confirm_default=_("Ja, verwijderen") %}
{% csrf_token %}
{% button icon="delete" text=_("Verwijderen") icon_outlined=True transparent=True %}
{% endrender_form %}
Expand Down
15 changes: 15 additions & 0 deletions src/open_inwoner/components/templates/components/Modal/Modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% load i18n icon_tags button_tags %}

<div class="modal" id="modal" aria-labelledby="modal__title" aria-describedby="modal__text">
jiromaykin marked this conversation as resolved.
Show resolved Hide resolved
<div class="modal__container">
<div class="modal__actions modal__actions--align-right">
{% button type="button" text=_("Sluiten") hide_text=True icon="close" outlined=True extra_classes="modal__button modal__close-title button__icon-close" %}
</div>
<h2 class="modal__title" id="modal__title"></h2>
<div class="modal__text" id="modal__text"></div>
<div class="modal__actions" id="modal__actions">
<button class="button modal__button button--error-confirm button-icon__confirm modal__confirm" type="submit" name="" value="" aria-label=""><span class="inner-text">Bevestig </span>{% icon icon="delete" icon_position="after" extra_classes="modal__visible-icon" outlined=True %}</button>
<button class="button modal__button modal__close button--primary button-icon--primary" type="button" name="" value="" aria-label=""><span class="inner-text">Sluiten </span>{% icon icon="cancel" icon_position="after" extra_classes="modal__visible-icon" outlined=True %}</button>
</div>
</div>
</div>
15 changes: 11 additions & 4 deletions src/open_inwoner/js/components/accessibility/help_modal.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Modal from '../modal'

class HelpModal {
export class HelpModal {
static selector = '.accessibility--modal'

constructor(helpButton) {
this.helpButton = helpButton
this.modal = document.querySelector('.help-modal')
Expand All @@ -11,16 +13,21 @@ class HelpModal {
event.preventDefault()
this.helpButton.classList.add('accessibility-header__modal--highlight')
const modalId = document.getElementById('modal')
jiromaykin marked this conversation as resolved.
Show resolved Hide resolved
// Differentiate this modal from others
modalId.classList.add('accessibility-modal')
const modal = new Modal(modalId)
modal.setTitle(this.modal.dataset.helpTitle)
modal.setText(this.modal.dataset.helpText)
modal.setModalIcons(false)
modal.setConfirmButtonVisibility(false)
modal.setCancelButtonVisibility(true)
modal.setClose(this.modal.dataset.helpClose)
modal.setModalClosedCallback(() => {
this.helpButton.classList.remove('accessibility-header__modal--highlight')
})
modal.show(this.helpButton)
}
}

const helpButton = document.querySelectorAll('.accessibility--modal')
;[...helpButton].forEach((button) => new HelpModal(button))
document
.querySelectorAll(HelpModal.selector)
.forEach((helpButton) => new HelpModal(helpButton))
19 changes: 14 additions & 5 deletions src/open_inwoner/js/components/confirmation/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Modal from '../modal'

class Confirmation {
export class Confirmation {
static selector = '.form.confirm'

constructor(form) {
this.real_submit = false
this.form = form
Expand All @@ -11,15 +13,21 @@ class Confirmation {
if (!this.real_submit) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

real_submit is a strange name. Is there any other kind of submit field? I know it's not your change, but perhaps consider changing this (unless it makes sense to you and I just misunderstand).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's not mine 🙂 but I understand why they chose this name: this name is often used when pop-up modals pop up after clicking something on the pages that just LOOKS like a Submit button, but actually isn't - and this modal warns the user about the data they are about to submit/delete - so not until they actually click the confirm button in the modal, does the "real" POST happen. So I think I'd like to keep this name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense!

event.preventDefault()
const modalId = document.getElementById('modal')
// Differentiate this modal from others
modalId.classList.add('confirm-modal')
const modal = new Modal(modalId)
modal.setTitle(this.form.dataset.confirmTitle)
// Only show confirmation if text is set
modal.setText(this.form.dataset.confirmText || '')
modal.setClose(this.form.dataset.confirmCancel)
modal.setModalIcons(true)
modal.setConfirmButtonVisibility(true)
modal.setCancelButtonVisibility(true)
modal.setButtonIconCloseVisibility(true)
modal.setClose(this.form.dataset.confirmCancel, 'button--primary-close')
modal.setConfirm(
this.form.dataset.confirmDefault,
this.handleConfirm.bind(this),
'button--danger'
'button--error-confirm'
)
modal.show(this.form)
}
Expand All @@ -32,5 +40,6 @@ class Confirmation {
}
}

const confirmForms = document.querySelectorAll('form.confirm')
;[...confirmForms].forEach((confirmForm) => new Confirmation(confirmForm))
document
.querySelectorAll(Confirmation.selector)
.forEach((confirmForm) => new Confirmation(confirmForm))
2 changes: 1 addition & 1 deletion src/open_inwoner/js/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import './map'
import './message-file'
import { Notification } from './notifications'
import './plans'
import './preview'
import './plan-preview'
import './questionnaire'
import './readmore'
import './search'
Expand Down
77 changes: 72 additions & 5 deletions src/open_inwoner/js/components/modal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export default class Modal {
this.actions = this.node.querySelector('.modal__actions')
this.close = this.node.querySelector('.modal__close')
this.confirm = this.node.querySelector('.modal__confirm')
this.closeInnerText = this.node.querySelector('.modal__close .inner-text')
this.confirmInnerText = this.node.querySelector(
'.modal__confirm .inner-text'
)
this.closeTitle = this.node.querySelector('.modal__close-title')

// This is for the prefilled modals so they will not be emptied
Expand All @@ -19,6 +23,9 @@ export default class Modal {
this.modalClosedCallback = null
this.setTitle('')
this.setText('')
this.setModalIcons(false)
this.setConfirmButtonVisibility(false)
this.setCancelButtonVisibility(false)
if (this.confirm) {
this.setConfirm('')
this.confirm.className = 'button modal__button modal__confirm'
Expand All @@ -43,6 +50,9 @@ export default class Modal {
this.node.addEventListener('close', () => {
this.hide()
})
document.addEventListener('keydown', (event) => {
this.escapeModal(event)
})
}

setTitle(text) {
Expand All @@ -53,13 +63,53 @@ export default class Modal {
this.text.innerText = text
}

setModalIcons(modalIcons) {
// Whether the modal-buttons should have icons or not
if (modalIcons) {
this.node.classList.add('show-modal-icons')
} else {
// Remove lingering elements if user did not close other modals
this.node.classList.remove('show-modal-icons')
}
}

setConfirmButtonVisibility(confirmVisibility) {
// Accessibility: whether the modal should have a confirm button or not
if (confirmVisibility) {
this.node.classList.add('show-confirm-button')
} else {
// Remove lingering elements if user did not close other modals
this.node.classList.remove('show-confirm-button')
}
}

setCancelButtonVisibility(cancelVisibility) {
// Accessibility: whether the modal should have a cancel button or not
if (cancelVisibility) {
this.node.classList.add('show-cancel-button')
} else {
// Remove lingering elements if user did not close other modals
this.node.classList.remove('show-cancel-button')
}
}

setButtonIconCloseVisibility(buttonIconCloseVisibility) {
// Whether the modal should have a top-right close button or not
if (buttonIconCloseVisibility) {
this.node.classList.add('show-button-icon-close')
} else {
// Remove lingering elements if user did not close other modals
this.node.classList.remove('show-button-icon-close')
}
}

setClose(text, className = 'button--primary') {
this.close.innerText = text
this.closeInnerText.innerText = text
this.close.classList.add(className)
}

setConfirm(text, callback, className = 'button--primary') {
this.confirm.innerText = text
this.confirmInnerText.innerText = text
this.confirm.onclick = (event) => {
callback(event)
this.hide()
Expand All @@ -75,12 +125,23 @@ export default class Modal {
this.node.classList.add('modal--open')
this.refocusOnClose = refocusOnClose
this.close.focus()
this.node.showModal()
}

hide() {
this.node.classList.remove('modal--open')
this.node.close()
const classesToRemove = [
'modal--open',
'confirm-modal',
'accessibility-modal',
'session-modal',
'show-button-icon-close',
'show-modal-icons',
'show-confirm-button',
'show-cancel-button',
'show-button-icon-close',
]
classesToRemove.forEach((className) =>
this.node.classList.remove(className)
)
if (this.refocusOnClose) {
this.refocusOnClose.focus()
this.refocusOnClose = null
Expand All @@ -89,4 +150,10 @@ export default class Modal {
this.modalClosedCallback()
}
}

escapeModal(event) {
if (event.key === 'Escape') {
this.hide()
}
}
}
28 changes: 28 additions & 0 deletions src/open_inwoner/js/components/plan-preview/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Modal from '../modal'

export class Preview {
// Selector for elements triggering the preview modal
static selector = '.show-preview'

constructor(node) {
this.node = node
this.node.addEventListener('click', this.openPreview.bind(this))
}

openPreview(event) {
event.stopPropagation()
event.preventDefault()

const modalId = document.getElementById(this.node.dataset.id)
const modal = new Modal(modalId)
modal.setModalIcons(false)
modal.setConfirmButtonVisibility(false)
modal.setCancelButtonVisibility(true)
modal.setButtonIconCloseVisibility(true)
modal.show()
}
}

document
.querySelectorAll(Preview.selector)
.forEach((previewNode) => new Preview(previewNode))
21 changes: 0 additions & 21 deletions src/open_inwoner/js/components/preview/index.js

This file was deleted.

6 changes: 6 additions & 0 deletions src/open_inwoner/js/components/session/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,15 @@ class SessionTimeout {

configureModal(title, bodyText, buttonText, callback) {
const modalId = document.getElementById('modal')
// Differentiate this modal from others
modalId.classList.add('session-modal')
const modal = new Modal(modalId)
modal.setTitle(title)
modal.setText(bodyText)
modal.setModalIcons(false)
modal.setConfirmButtonVisibility(true)
modal.setCancelButtonVisibility(false)
modal.setButtonIconCloseVisibility(true)
modal.setConfirm(buttonText, callback.bind(this))
modal.show()
}
Expand Down
11 changes: 11 additions & 0 deletions src/open_inwoner/scss/components/Button/Button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,17 @@
color: var(--color-error-darker);
}

&--primary-close {
background-color: var(--color-primary);
border: 1px solid var(--color-primary);
color: var(--color-font-primary);
}

&--error-confirm {
background-color: var(--color-error-darker);
color: var(--color-white);
}

&--borderless {
background-color: rgba(0, 0, 0, 0);
border: none;
Expand Down
29 changes: 28 additions & 1 deletion src/open_inwoner/scss/components/Plan/PlanCreate.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@
}

.modal--open {
background: rgba(0, 0, 0, 0.25);
.modal__container {
padding: var(--spacing-giant);

[class*='icon'] {
font-size: var(--font-size-body-large);
}

.button-icon--primary {
color: var(--color-white);
font-size: var(--font-size-body);
transform: none;
}

.modal__actions {
.modal__button {
&:focus-visible {
box-shadow: none;
outline: none;
}
}
}

.modal__title .modal__button {
height: var(--font-size-body);
min-width: initial;
padding: 0;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

*[class*='icon'],
*[class*='Icon'] {
color: #4b4b4b;
font-size: 14px;
color: var(--color-gray-dark);
font-size: var(--font-size-body-small);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
padding-bottom: var(--spacing-large);
}

h2,
.h2 {
display: block;
height: initial;
Expand Down
Loading
Loading