feat: add navigation to gallery / dialog
This commit is contained in:
parent
51dbd48e67
commit
8b542139b5
6 changed files with 90 additions and 14 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
1
src/assets/svg/misc/arrow-left.svg
Normal file
1
src/assets/svg/misc/arrow-left.svg
Normal 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 |
1
src/assets/svg/misc/arrow-right.svg
Normal file
1
src/assets/svg/misc/arrow-right.svg
Normal 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 |
Loading…
Reference in a new issue