| --- |
| |
| import ResponsiveImage from "./ResponsiveImage.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"> |
| <ResponsiveImage |
| 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"> |
| <ResponsiveImage |
| 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-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; |
| } |
|
|
| .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: 200px; |
| 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> |
|
|