CoolFace
Apppublic

lerobot/robot-learning-tutorial

sourceHugging Faceupdated 1y agoView on Hugging Face
508likes
MultiImage.astro320 linesDownload Raw Back to components
1---2// @ts-ignore - types provided by Astro at runtime3import ResponsiveImage from "./ResponsiveImage.astro";4 5interface ImageItem {6    /** Source image imported via astro:assets */7    src: any;8    /** Alt text for accessibility */9    alt: string;10    /** Individual caption for this image */11    caption?: string;12    /** Optional individual image ID for referencing */13    id?: string;14    /** Enable zoom on this specific image (defaults to parent zoomable setting) */15    zoomable?: boolean;16    /** Enable download on this specific image (defaults to parent downloadable setting) */17    downloadable?: boolean;18}19 20interface Props {21    /** Array of images to display */22    images: ImageItem[];23    /** Global caption for the entire figure */24    caption?: string;25    /** Layout mode: number of columns or 'auto' for responsive */26    layout?: "2-column" | "3-column" | "4-column" | "auto";27    /** Enable medium-zoom behavior on all images (can be overridden per image) */28    zoomable?: boolean;29    /** Show download buttons on all images (can be overridden per image) */30    downloadable?: boolean;31    /** Optional class to apply on the wrapper */32    class?: string;33    /** Optional global ID for the multi-image figure */34    id?: string;35}36 37const {38    images,39    caption,40    layout = "3-column",41    zoomable = false,42    downloadable = false,43    class: className,44    id,45} = Astro.props as Props;46 47const hasCaptionSlot = Astro.slots.has("caption");48const hasCaption =49    hasCaptionSlot || (typeof caption === "string" && caption.length > 0);50const uid = `mi_${Math.random().toString(36).slice(2)}`;51 52// Generate CSS grid columns based on layout53const getGridColumns = () => {54    switch (layout) {55        case "2-column":56            return "repeat(2, 1fr)";57        case "3-column":58            return "repeat(3, 1fr)";59        case "4-column":60            return "repeat(4, 1fr)";61        case "auto":62            return "repeat(auto-fit, minmax(200px, 1fr))";63        default:64            return "repeat(3, 1fr)";65    }66};67 68const gridColumns = getGridColumns();69---70 71<div72    class={`multi-image ${className || ""}`}73    data-mi-root={uid}74    data-layout={layout}75    {id}76>77    {78        hasCaption ? (79            <figure class="multi-image-figure">80                <div81                    class="multi-image-grid"82                    style={`grid-template-columns: ${gridColumns}`}83                >84                    {images.map((image, index) => (85                        <div class="multi-image-item">86                            <ResponsiveImage87                                src={image.src}88                                alt={image.alt}89                                zoomable={image.zoomable ?? zoomable}90                                downloadable={91                                    image.downloadable ?? downloadable92                                }93                                class="multi-image-img"94                            />95                            {image.caption && (96                                <div class="multi-image-subcaption">97                                    {image.caption}98                                </div>99                            )}100                            {image.id && (101                                <span102                                    id={image.id}103                                    style="position: absolute;"104                                />105                            )}106                        </div>107                    ))}108                </div>109                <figcaption class="multi-image-caption">110                    {hasCaptionSlot ? (111                        <slot name="caption" />112                    ) : (113                        caption && <span set:html={caption} />114                    )}115                </figcaption>116            </figure>117        ) : (118            <div119                class="multi-image-grid"120                style={`grid-template-columns: ${gridColumns}`}121            >122                {images.map((image, index) => (123                    <div class="multi-image-item">124                        <ResponsiveImage125                            src={image.src}126                            alt={image.alt}127                            zoomable={image.zoomable ?? zoomable}128                            downloadable={image.downloadable ?? downloadable}129                            class="multi-image-img"130                        />131                        {image.caption && (132                            <div class="multi-image-subcaption">133                                {image.caption}134                            </div>135                        )}136                        {image.id && (137                            <span id={image.id} style="position: absolute;" />138                        )}139                    </div>140                ))}141            </div>142        )143    }144</div>145 146<style>147    .multi-image {148        margin: var(--block-spacing-y) 0;149    }150 151    .multi-image-figure {152        margin: 0;153    }154 155    .multi-image-grid {156        display: grid;157        gap: 1rem;158        align-items: start;159    }160 161    .multi-image-item {162        display: flex;163        flex-direction: column;164        text-align: center;165        position: relative;166        z-index: var(--z-content);167        transition: z-index 0.3s ease;168    }169 170    /* Quand medium-zoom est actif, masquer temporairement les autres images du multi-image */171    :global(.medium-zoom--opened) .multi-image-item {172        opacity: 0;173        z-index: calc(var(--z-base) - 1);174        transition:175            opacity 0.3s ease,176            z-index 0.3s ease;177    }178 179    /* L'image actuellement zoomée reste visible */180    :global(.medium-zoom--opened)181        .multi-image-item:has(:global(.medium-zoom--opened)) {182        opacity: 1;183        z-index: var(--z-overlay);184    }185 186    /* Fallback pour navigateurs sans support :has() */187    :global(.medium-zoom--opened) .multi-image-item.zoom-active {188        opacity: 1 !important;189        z-index: var(--z-overlay) !important;190    }191 192    .multi-image-item :global(.ri-root) {193        margin: 0;194    }195 196    .multi-image-item :global(figure) {197        margin: 0;198    }199 200    .multi-image-img {201        width: 100%;202        height: auto;203        object-fit: contain;204    }205 206    .multi-image-subcaption {207        font-size: 0.85rem;208        color: var(--muted-color);209        margin-top: 0.5rem;210        line-height: 1.4;211    }212 213    .multi-image-caption {214        text-align: left;215        font-size: 0.9rem;216        color: var(--muted-color);217        margin-top: 1rem;218        line-height: 1.4;219    }220 221    /* Responsive behavior */222    @media (max-width: 768px) {223        .multi-image-grid[style*="repeat(3, 1fr)"],224        .multi-image-grid[style*="repeat(4, 1fr)"] {225            grid-template-columns: 1fr !important;226            gap: 1.5rem;227        }228 229        .multi-image-grid[style*="repeat(2, 1fr)"] {230            grid-template-columns: 1fr !important;231            gap: 1.5rem;232        }233    }234 235    @media (min-width: 769px) and (max-width: 1024px) {236        .multi-image-grid[style*="repeat(4, 1fr)"] {237            grid-template-columns: repeat(2, 1fr) !important;238        }239    }240 241    /* Equal height images when desired */242    .multi-image[data-layout*="column"] .multi-image-item :global(img) {243        height: 200px;244        object-fit: contain;245    }246 247    /* Auto layout gets flexible heights */248    .multi-image[data-layout="auto"] .multi-image-item :global(img) {249        height: auto;250    }251 252    /* Ensure images maintain aspect ratio */253    .multi-image-item :global(img) {254        max-width: 100%;255        display: block;256        margin: 0 auto;257    }258</style>259 260<script>261    // Enhanced medium-zoom integration for MultiImage262    document.addEventListener("DOMContentLoaded", () => {263        // Amélioration du comportement des MultiImage avec medium-zoom264        const multiImages = document.querySelectorAll(".multi-image");265 266        multiImages.forEach((multiImage) => {267            const items = multiImage.querySelectorAll(".multi-image-item");268            const zoomableImages = multiImage.querySelectorAll(269                'img[data-zoomable="1"]',270            );271 272            zoomableImages.forEach((img) => {273                img.addEventListener("click", () => {274                    // Trouver l'item parent de l'image cliquée et le ri-root275                    const activeItem = img.closest(".multi-image-item");276                    const riRoot = img.closest(".ri-root");277 278                    // Nettoyer TOUS les zoom-active (MultiImage items et ResponsiveImage)279                    document280                        .querySelectorAll(281                            ".multi-image-item.zoom-active, .ri-root.zoom-active",282                        )283                        .forEach((el) => el.classList.remove("zoom-active"));284 285                    // Ajouter zoom-active aux éléments actifs286                    if (activeItem) {287                        activeItem.classList.add("zoom-active");288                    }289                    if (riRoot) {290                        riRoot.classList.add("zoom-active");291                    }292                });293            });294        });295 296        // Nettoyer TOUTES les classes lors de la fermeture du zoom297        document.addEventListener("click", (e) => {298            if (e.target.classList.contains("medium-zoom-overlay")) {299                // Zoom fermé, nettoyer toutes les classes zoom-active300                document301                    .querySelectorAll(302                        ".multi-image-item.zoom-active, .ri-root.zoom-active",303                    )304                    .forEach((item) => item.classList.remove("zoom-active"));305            }306        });307 308        // Écouter les événements clavier pour fermer le zoom309        document.addEventListener("keydown", (e) => {310            if (e.key === "Escape") {311                document312                    .querySelectorAll(313                        ".multi-image-item.zoom-active, .ri-root.zoom-active",314                    )315                    .forEach((item) => item.classList.remove("zoom-active"));316            }317        });318    });319</script>320