feat: add navigation to gallery / dialog

This commit is contained in:
madrilene 2025-06-16 13:13:22 +02:00
parent 51dbd48e67
commit 8b542139b5
6 changed files with 90 additions and 14 deletions

View file

@ -61,7 +61,9 @@ export const details = {
collapse: 'collapse all'
};
export const dialog = {
close: 'Close'
close: 'Close',
next: 'Next',
previous: 'Previous'
};
export const navigation = {
navLabel: 'Menu',

View file

@ -1,8 +1,18 @@
<is-land on:idle>
<div class="gallery | grid mt-l-xl" role="list">
<div class="gallery | grid mt-l-xl gutter-xs" role="list">
{%- for item in gallery -%}
<dialog class="flow modal{{ loop.index }}">
<button class="button" autofocus>{{ meta.dialog.close }}</button>
<div class="cluster justify-center flow-space-m gutter-s">
<button data-nav="prev" class="button text-step-1" data-button-variant="primary">
<span class="visually-hidden">{{ meta.dialog.previous }}</span>{% svg "misc/arrow-left" %}
</button>
<button data-close class="button" autofocus data-button-variant="primary">
{{ meta.dialog.close }}
</button>
<button data-nav="next" class="button text-step-1" data-button-variant="primary">
<span class="visually-hidden">{{ meta.dialog.next }}</span>{% svg "misc/arrow-right" %}
</button>
</div>
{%- image item.image, item.alt, item.caption -%}
</dialog>
<button data-index="{{ loop.index }}">{%- image item.image, item.alt -%}</button>

View file

@ -9,6 +9,11 @@
cursor: pointer;
}
.gallery dialog + button:focus-visible {
outline: 3px solid var(--focus-color, currentColor);
outline-offset: var(--focus-offset, 0.3ch);
}
/* the close button */
.gallery dialog > button {
margin-inline: auto;

View file

@ -1,22 +1,79 @@
// manages the behavior of modal several dialogs on a page: open / close buttons and light dismiss.
const buttons = document.querySelectorAll('button[data-index]');
const modals = document.querySelectorAll('dialog');
const closeButtons = document.querySelectorAll('dialog button');
buttons.forEach((button, index) => {
const modals = Array.from(document.querySelectorAll('dialog'));
const openButtons = document.querySelectorAll('button[data-index]');
openButtons.forEach((button, index) => {
button.addEventListener('click', () => {
modals[index].showModal();
modals[index].focus();
});
});
closeButtons.forEach((button, index) => {
button.addEventListener('click', () => {
modals[index].close();
modals.forEach((modal, index) => {
const closeBtn = modal.querySelector('button[data-close]');
const prevBtn = modal.querySelector('button[data-nav="prev"]');
const nextBtn = modal.querySelector('button[data-nav="next"]');
closeBtn?.addEventListener('click', () => modal.close());
if (!prevBtn && !nextBtn) return;
prevBtn?.addEventListener('click', () => {
modal.close();
const prev = modals[(index - 1 + modals.length) % modals.length];
prev.showModal();
});
});
window.addEventListener('click', event => {
modals.forEach(modal => {
if (event.target === modal) {
nextBtn?.addEventListener('click', () => {
modal.close();
const next = modals[(index + 1) % modals.length];
next.showModal();
});
// arrow navigation
modal.addEventListener('keydown', event => {
if (event.key === 'ArrowRight') {
event.preventDefault();
const next = modals[(index + 1) % modals.length];
modal.close();
next.showModal();
} else if (event.key === 'ArrowLeft') {
event.preventDefault();
const prev = modals[(index - 1 + modals.length) % modals.length];
modal.close();
prev.showModal();
}
});
// swipe navigation
let startX = 0;
let endX = 0;
modal.addEventListener('touchstart', event => {
if (event.touches.length > 1) return;
startX = event.touches[0].screenX;
});
modal.addEventListener('touchend', event => {
if (event.changedTouches.length > 1) return;
endX = event.changedTouches[0].screenX;
handleSwipe();
});
function handleSwipe() {
const diff = endX - startX;
if (Math.abs(diff) < 50) return;
modal.close();
const target =
diff > 0
? modals[(index - 1 + modals.length) % modals.length] // swipe right → prev
: modals[(index + 1) % modals.length]; // swipe left → next
target.showModal();
target.focus();
}
});
window.addEventListener('click', event => {
modals.forEach(modal => {
if (event.target === modal) modal.close();
});
});

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>

After

Width:  |  Height:  |  Size: 235 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>

After

Width:  |  Height:  |  Size: 234 B