|
|
--- |
|
|
|
|
|
import Image from "./Image.astro"; |
|
|
|
|
|
interface ImageItem { |
|
|
|
|
|
src: any; |
|
|
|
|
|
alt: string; |
|
|
|
|
|
caption?: string; |
|
|
|
|
|
id?: string; |
|
|
|
|
|
zoomable?: boolean; |
|
|
|
|
|
downloadable?: boolean; |
|
|
} |
|
|
|
|
|
interface Props { |
|
|
|
|
|
images: ImageItem[]; |
|
|
|
|
|
caption?: string; |
|
|
|
|
|
layout?: "2-column" | "3-column" | "4-column" | "auto"; |
|
|
|
|
|
zoomable?: boolean; |
|
|
|
|
|
downloadable?: boolean; |
|
|
|
|
|
class?: string; |
|
|
|
|
|
id?: string; |
|
|
} |
|
|
|
|
|
const { |
|
|
images, |
|
|
caption, |
|
|
layout = "3-column", |
|
|
zoomable = false, |
|
|
downloadable = false, |
|
|
class: className, |
|
|
id, |
|
|
} = Astro.props as Props; |
|
|
|
|
|
const hasCaptionSlot = Astro.slots.has("caption"); |
|
|
const hasCaption = |
|
|
hasCaptionSlot || (typeof caption === "string" && caption.length > 0); |
|
|
const uid = `mi_${Math.random().toString(36).slice(2)}`; |
|
|
|
|
|
|
|
|
const getGridColumns = () => { |
|
|
switch (layout) { |
|
|
case "2-column": |
|
|
return "repeat(2, 1fr)"; |
|
|
case "3-column": |
|
|
return "repeat(3, 1fr)"; |
|
|
case "4-column": |
|
|
return "repeat(4, 1fr)"; |
|
|
case "auto": |
|
|
return "repeat(auto-fit, minmax(200px, 1fr))"; |
|
|
default: |
|
|
return "repeat(3, 1fr)"; |
|
|
} |
|
|
}; |
|
|
|
|
|
const gridColumns = getGridColumns(); |
|
|
--- |
|
|
|
|
|
<div |
|
|
class={`multi-image ${className || ""}`} |
|
|
data-mi-root={uid} |
|
|
data-layout={layout} |
|
|
{id} |
|
|
> |
|
|
{ |
|
|
hasCaption ? ( |
|
|
<figure class="multi-image-figure"> |
|
|
<div |
|
|
class="multi-image-grid" |
|
|
style={`grid-template-columns: ${gridColumns}`} |
|
|
> |
|
|
{images.map((image, index) => ( |
|
|
<div class="multi-image-item"> |
|
|
<Image |
|
|
src={image.src} |
|
|
alt={image.alt} |
|
|
zoomable={image.zoomable ?? zoomable} |
|
|
downloadable={ |
|
|
image.downloadable ?? downloadable |
|
|
} |
|
|
class="multi-image-img" |
|
|
/> |
|
|
{image.caption && ( |
|
|
<div class="multi-image-subcaption"> |
|
|
{image.caption} |
|
|
</div> |
|
|
)} |
|
|
{image.id && ( |
|
|
<span |
|
|
id={image.id} |
|
|
style="position: absolute;" |
|
|
/> |
|
|
)} |
|
|
</div> |
|
|
))} |
|
|
</div> |
|
|
<figcaption class="multi-image-caption"> |
|
|
{hasCaptionSlot ? ( |
|
|
<slot name="caption" /> |
|
|
) : ( |
|
|
caption && <span set:html={caption} /> |
|
|
)} |
|
|
</figcaption> |
|
|
</figure> |
|
|
) : ( |
|
|
<div |
|
|
class="multi-image-grid" |
|
|
style={`grid-template-columns: ${gridColumns}`} |
|
|
> |
|
|
{images.map((image, index) => ( |
|
|
<div class="multi-image-item"> |
|
|
<Image |
|
|
src={image.src} |
|
|
alt={image.alt} |
|
|
zoomable={image.zoomable ?? zoomable} |
|
|
downloadable={image.downloadable ?? downloadable} |
|
|
class="multi-image-img" |
|
|
/> |
|
|
{image.caption && ( |
|
|
<div class="multi-image-subcaption"> |
|
|
{image.caption} |
|
|
</div> |
|
|
)} |
|
|
{image.id && ( |
|
|
<span id={image.id} style="position: absolute;" /> |
|
|
)} |
|
|
</div> |
|
|
))} |
|
|
</div> |
|
|
) |
|
|
} |
|
|
</div> |
|
|
|
|
|
<style> |
|
|
.multi-image { |
|
|
margin: var(--block-spacing-y) 0; |
|
|
} |
|
|
|
|
|
.multi-image-figure { |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
.multi-image-grid { |
|
|
display: grid; |
|
|
gap: 1rem; |
|
|
align-items: start; |
|
|
} |
|
|
|
|
|
.multi-image-item { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
text-align: center; |
|
|
position: relative; |
|
|
z-index: var(--z-content); |
|
|
transition: z-index 0.3s ease; |
|
|
} |
|
|
|
|
|
|
|
|
:global(.medium-zoom--opened) .multi-image-item { |
|
|
opacity: 0; |
|
|
z-index: calc(var(--z-base) - 1); |
|
|
transition: |
|
|
opacity 0.3s ease, |
|
|
z-index 0.3s ease; |
|
|
} |
|
|
|
|
|
|
|
|
:global(.medium-zoom--opened) .multi-image-subcaption { |
|
|
display: none !important; |
|
|
} |
|
|
|
|
|
|
|
|
:global(.medium-zoom--opened) .multi-image { |
|
|
z-index: -1 !important; |
|
|
} |
|
|
|
|
|
|
|
|
:global(.medium-zoom--opened) .multi-image-caption { |
|
|
display: none !important; |
|
|
} |
|
|
|
|
|
|
|
|
:global(.medium-zoom--opened) |
|
|
.multi-image-item:has(:global(.medium-zoom--opened)) { |
|
|
opacity: 1; |
|
|
z-index: var(--z-overlay); |
|
|
} |
|
|
|
|
|
|
|
|
:global(.medium-zoom--opened) .multi-image-item.zoom-active { |
|
|
opacity: 1 !important; |
|
|
z-index: var(--z-overlay) !important; |
|
|
} |
|
|
|
|
|
|
|
|
:global(.medium-zoom--opened) |
|
|
.multi-image-item.zoom-active |
|
|
.multi-image-subcaption { |
|
|
opacity: 1 !important; |
|
|
z-index: var(--z-overlay) !important; |
|
|
} |
|
|
|
|
|
.multi-image-item :global(.ri-root) { |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
.multi-image-item :global(figure) { |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
.multi-image-img { |
|
|
width: 100%; |
|
|
height: auto; |
|
|
object-fit: contain; |
|
|
} |
|
|
|
|
|
.multi-image-subcaption { |
|
|
font-size: 0.85rem; |
|
|
color: var(--muted-color); |
|
|
margin-top: 0.5rem; |
|
|
line-height: 1.4; |
|
|
} |
|
|
|
|
|
.multi-image-caption { |
|
|
text-align: left; |
|
|
font-size: 0.9rem; |
|
|
color: var(--muted-color); |
|
|
margin-top: 1rem; |
|
|
line-height: 1.4; |
|
|
} |
|
|
|
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.multi-image-grid[style*="repeat(3, 1fr)"], |
|
|
.multi-image-grid[style*="repeat(4, 1fr)"] { |
|
|
grid-template-columns: 1fr !important; |
|
|
gap: 1.5rem; |
|
|
} |
|
|
|
|
|
.multi-image-grid[style*="repeat(2, 1fr)"] { |
|
|
grid-template-columns: 1fr !important; |
|
|
gap: 1.5rem; |
|
|
} |
|
|
} |
|
|
|
|
|
@media (min-width: 769px) and (max-width: 1024px) { |
|
|
.multi-image-grid[style*="repeat(4, 1fr)"] { |
|
|
grid-template-columns: repeat(2, 1fr) !important; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.multi-image[data-layout*="column"] .multi-image-item :global(img) { |
|
|
height: auto; |
|
|
object-fit: contain; |
|
|
} |
|
|
|
|
|
|
|
|
.multi-image[data-layout="auto"] .multi-image-item :global(img) { |
|
|
height: auto; |
|
|
} |
|
|
|
|
|
|
|
|
.multi-image-item :global(img) { |
|
|
max-width: 100%; |
|
|
display: block; |
|
|
margin: 0 auto; |
|
|
} |
|
|
</style> |
|
|
|
|
|
<script> |
|
|
|
|
|
document.addEventListener("DOMContentLoaded", () => { |
|
|
|
|
|
const multiImages = document.querySelectorAll(".multi-image"); |
|
|
|
|
|
multiImages.forEach((multiImage) => { |
|
|
const items = multiImage.querySelectorAll(".multi-image-item"); |
|
|
const zoomableImages = multiImage.querySelectorAll( |
|
|
'img[data-zoomable="1"]', |
|
|
); |
|
|
|
|
|
zoomableImages.forEach((img) => { |
|
|
img.addEventListener("click", () => { |
|
|
|
|
|
const activeItem = img.closest(".multi-image-item"); |
|
|
const riRoot = img.closest(".ri-root"); |
|
|
|
|
|
|
|
|
document |
|
|
.querySelectorAll( |
|
|
".multi-image-item.zoom-active, .ri-root.zoom-active", |
|
|
) |
|
|
.forEach((el) => el.classList.remove("zoom-active")); |
|
|
|
|
|
|
|
|
if (activeItem) { |
|
|
activeItem.classList.add("zoom-active"); |
|
|
} |
|
|
if (riRoot) { |
|
|
riRoot.classList.add("zoom-active"); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener("click", (e) => { |
|
|
if (e.target.classList.contains("medium-zoom-overlay")) { |
|
|
|
|
|
document |
|
|
.querySelectorAll( |
|
|
".multi-image-item.zoom-active, .ri-root.zoom-active", |
|
|
) |
|
|
.forEach((item) => item.classList.remove("zoom-active")); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener("keydown", (e) => { |
|
|
if (e.key === "Escape") { |
|
|
document |
|
|
.querySelectorAll( |
|
|
".multi-image-item.zoom-active, .ri-root.zoom-active", |
|
|
) |
|
|
.forEach((item) => item.classList.remove("zoom-active")); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
</script> |
|
|
|