-
Notifications
You must be signed in to change notification settings - Fork 3
[TWE-661] Add sticky call to action and new link type for all call to actions #402
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
base: main
Are you sure you want to change the base?
Changes from all commits
f1de4fd
095519e
ce6eeec
90f443e
0c2cfb3
3db8c41
50b064f
04b5967
95d3454
0bd4016
c3f3543
7b47db3
c25afd6
69d69de
4b872db
4710e9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{% extends "atoms/icon_buttons/icon_button.html" %} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<button class="icon-button{% if modifier %} icon-button--{{ modifier }}{% endif %}{% if inverse %} icon-button--inverse{% endif %}{% if hidden %} hidden{% endif %}" {% if modifier and name %}data-{{ name }}-{{ modifier }}{% endif %}{% if data %} {{ data }}{% endif %}{% if aria %} {{ aria }}{% endif %}> | ||
<span class="u-sr-only"> | ||
{% if modifier %}{{ modifier }}{% endif %} | ||
</span> | ||
{% if modifier == 'next' %} | ||
<span class="rotate">{% include "patterns/atoms/icons/icon.html" with name="chevron" %}</span> | ||
{% elif modifier == 'previous' %} | ||
<span class="rotate rotate--left">{% include "patterns/atoms/icons/icon.html" with name="chevron" %}</span> | ||
{% elif modifier == 'down' %} | ||
<span>{% include "patterns/atoms/icons/icon.html" with name="chevron" %}</span> | ||
{% elif modifier %} | ||
<span>{% include "patterns/atoms/icons/icon.html" with name=modifier %}</span> | ||
{% endif %} | ||
</button> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,12 +11,40 @@ <h2 class="heading heading--two-b call-to-action__heading">{{ value.text }}</h2> | |
{% endif %} | ||
</div> | ||
{% if value.button_text and value.button_link %} | ||
<a href="{{ value.get_button_link }}" class="call-to-action__button button"> | ||
{{ value.button_text }} | ||
{% if value.get_button_link_block.block_type == "document_link" %} | ||
({{ value.get_button_file_size|filesizeformat }}) | ||
{% endif %} | ||
</a> | ||
{% if value.get_button_link_block.block_type == "modal_iframe" %} | ||
<button class="call-to-action__button button" data-micromodal-trigger="iframe-embed-modal">{{ value.button_text }}</button> | ||
{% else %} | ||
<a href="{{ value.get_button_link }}" class="call-to-action__button button"> | ||
{{ value.button_text }} | ||
{% if value.get_button_link_block.block_type == "document_link" %} | ||
({{ value.get_button_file_size|filesizeformat }}) | ||
{% endif %} | ||
</a> | ||
{% endif %} | ||
{% endif %} | ||
</div> | ||
</div> | ||
|
||
{% if value.get_button_link_block.block_type == "modal_iframe" %} | ||
<!-- Modal content --> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: can we create a separate reusable modal component for maintainability? It's the same in all 3 cases. |
||
<div class="modal" id="iframe-embed-modal" aria-hidden="true"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: should use unique IDs if we want to support multiple modals on the page. Otherwise, multiple modals with iframes won't work as expected. |
||
<div class="modal__overlay" data-micromodal-close></div> | ||
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-title" > | ||
<header class="modal__header"> | ||
<h2 class="modal__heading heading heading--two" id="modal-title">Service Enquiry</h2> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: should we make the modal title configurable instead of hard-coding it? Especially important if we want to support multiple modals on the page |
||
<div class="modal__close"> | ||
{% include "patterns/atoms/icon_buttons/icon_button.html" with modifier="close" data="data-micromodal-close" aria='aria-label="Close modal"' %} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: It looks like we don't have an icon with the name "close", so nothing is being rendered here. We could either use an existing icon from the sprites file, or add a new one |
||
</div> | ||
</header> | ||
<main class="modal__content" id="filters-content"> | ||
<iframe | ||
src="{{ cta.value.get_button_link }}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: should use |
||
width="100%" | ||
height="650px" | ||
frameborder="0" | ||
title="Modal Webform"> | ||
</iframe> | ||
</main> | ||
</div> | ||
</div> | ||
{% endif %} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
{% load wagtailcore_tags %} | ||
{# call_to_action is a required field (streamblock) in the block definition #} | ||
|
||
|
||
<div class="call-to-action__sticky" data-sticky-cta> | ||
<div class="call-to-action__inner"> | ||
{% if value.get_button_link_block.block_type == "modal_iframe" %} | ||
<button class="call-to-action__stickybutton button" data-micromodal-trigger="iframe-embed-modal"> | ||
<span class="call-to-action__stickyheading">{{ value.sticky_text }}</span> | ||
{% if value.sticky_subtext %} | ||
<br><span class="call-to-action__stickysubtext">{{ value.sticky_subtext }}</span> | ||
{% endif %} | ||
</button> | ||
{% else %} | ||
<a href="{{ value.get_button_link }}" class="call-to-action__button button"> | ||
<span class="call-to-action__stickyheading">{{ value.sticky_text }}</span> | ||
{% if value.get_button_link_block.block_type == "document_link" %} | ||
({{ value.get_button_file_size|filesizeformat }}) | ||
{% endif %} | ||
{% if value.sticky_subtext %} | ||
<br><span class="call-to-action__stickysubtext">{{ value.sticky_subtext }}</span> | ||
{% endif %} | ||
</a> | ||
{% endif %} | ||
</div> | ||
</div> | ||
|
||
{% if value.get_button_link_block.block_type == "modal_iframe" %} | ||
<!-- Modal content --> | ||
<div class="modal" id="iframe-embed-modal" aria-hidden="true"> | ||
<div class="modal__overlay" data-micromodal-close></div> | ||
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-title" > | ||
<header class="modal__header"> | ||
<h2 class="modal__heading heading heading--two" id="modal-title">Service Enquiry</h2> | ||
<div class="modal__close"> | ||
{% include "patterns/atoms/icon_buttons/icon_button.html" with modifier="close" data="data-micromodal-close" aria='aria-label="Close modal"' %} | ||
</div> | ||
</header> | ||
<main class="modal__content" id="filters-content"> | ||
<iframe | ||
src="{{ value.get_button_link }}" | ||
width="100%" | ||
height="650px" | ||
frameborder="0" | ||
title="Modal Webform"> | ||
</iframe> | ||
</main> | ||
</div> | ||
</div> | ||
{% endif %} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
context: | ||
value: | ||
sticky_text: Get in touch | ||
sticky_subtext: learn about our journey | ||
button_link: | ||
- modal_iframe: | ||
url: 'https://example.com' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import MicroModal from 'micromodal'; // es6 module | ||
|
||
// Assumes a strcuture as follows | ||
// <div class="modal" id="filters" aria-hidden="true"> | ||
// <div class="modal__overlay" tabindex="-1" data-micromodal-close></div> | ||
// <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-title" > | ||
// <header class="modal__header"> | ||
// <h2 class="modal__heading heading heading--two" id="modal-title">Title<h2> | ||
// <div class="modal__close"> | ||
// {% include "atoms/icon_buttons/icon_button.html" with modifier="close" data="data-micromodal-close" aria='aria-label="Close modal"' %} | ||
// </div> | ||
// </header> | ||
// <main class="modal__content" id="filters-content"> | ||
// Content | ||
// </main> | ||
// <footer class="modal__footer"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: remove the modal footer as we don't have it in our implementation. |
||
// <button class="modal__btn" data-micromodal-close>Close</button> | ||
// </footer> | ||
// </div> | ||
// </div> | ||
|
||
class Modal { | ||
static selector() { | ||
return '[data-micromodal-trigger]'; | ||
} | ||
|
||
constructor() { | ||
if (typeof MicroModal !== 'undefined') { | ||
MicroModal.init({ | ||
openTrigger: 'data-micromodal-trigger', | ||
disableScroll: true, | ||
}); | ||
} | ||
|
||
Modal.bindEvents(); | ||
} | ||
|
||
static bindEvents() { | ||
// Listen for clicks on the document instead of using micromodel default, which doesn't work with htmx | ||
document.body.addEventListener('click', Modal.handleEvent); | ||
document.body.addEventListener('touchstart', Modal.handleEvent); | ||
document.body.addEventListener('keydown', Modal.handleKeyDown); | ||
} | ||
|
||
static handleEvent(event) { | ||
const trigger = event.target.closest('[data-micromodal-trigger]'); | ||
if (trigger) { | ||
event.preventDefault(); | ||
event.stopPropagation(); // Stop the event from bubbling up | ||
|
||
// Get the modal ID and open the correct modal | ||
const modalId = trigger.getAttribute('data-micromodal-trigger'); | ||
if (modalId && typeof MicroModal !== 'undefined') { | ||
MicroModal.show(modalId); | ||
// Ensure tabbing forward from iframes stays within the modal | ||
Modal.ensurePostIframeFocusTrap(modalId); | ||
} | ||
} | ||
|
||
// Close modal when clicking on close buttons | ||
const closeButton = event.target.closest( | ||
'[data-micromodal-close], [data-listing-submit]', | ||
); | ||
if (closeButton) { | ||
const modal = closeButton.closest('.modal'); | ||
if (modal) { | ||
MicroModal.close(modal.id); // Close the modal | ||
document.body.style.overflow = ''; // Remove overflow hidden from body | ||
} | ||
} | ||
} | ||
|
||
// Prevent Enter from closing modal unless on button | ||
static handleKeyDown(event) { | ||
if (event.key === 'Enter') { | ||
const modal = event.target.closest('.modal'); | ||
if (modal) { | ||
// Allow Enter on buttons and prevent it on everything else | ||
if (event.target.tagName !== 'BUTTON') { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// When a modal contains an iframe, browser-level tabbing inside the iframe | ||
// does not bubble key events to the parent, so focus-trap libraries | ||
// cannot reliably intercept the Tab press. Add a focus sentinel immediately | ||
// after the iframe that redirects focus to the Close button. | ||
static ensurePostIframeFocusTrap(modalId) { | ||
const modal = document.getElementById(modalId); | ||
if (!modal) return; | ||
const container = modal.querySelector('.modal__container'); | ||
if (!container) return; | ||
|
||
const iframe = container.querySelector('iframe'); | ||
if (!iframe) return; | ||
|
||
// Only add once per modal instance | ||
if (container.querySelector('.modal__focus-sentinel')) return; | ||
|
||
const sentinel = document.createElement('span'); | ||
sentinel.tabIndex = 0; | ||
|
||
sentinel.addEventListener('focus', () => { | ||
const closeButton = modal.querySelector('[data-micromodal-close]'); | ||
if (closeButton) { | ||
closeButton.focus(); | ||
} | ||
}); | ||
|
||
iframe.parentNode.insertBefore(sentinel, iframe.nextSibling); | ||
} | ||
} | ||
|
||
export default Modal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: This seems to be redundant as we already have an aria attribute on the button.
For the modal, we pass "Close modal" as an aria label, and we also pass "close" as a modifier, so the screen reader will announce both of these.
Also, names of the icons are usually not descriptive enough to act as proper labels for screen reader users.