aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
441 lines (438 loc) • 18.4 kB
JavaScript
'use client';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { Grid3X3, List, ZoomIn, Heart, Share2, Eye, X, ChevronLeft, ChevronRight } from 'lucide-react';
import { useState, useCallback } from 'react';
import '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import { MotionFramer } from '../../primitives/motion/MotionFramer.js';
import { GlassButton } from '../button/GlassButton.js';
import '../button/GlassFab.js';
import '../button/GlassMagneticButton.js';
import { CardContent } from '../card/index.js';
import '../data-display/GlassAccordion.js';
import '../data-display/GlassAlert.js';
import '../data-display/GlassAvatar.js';
import { GlassBadge } from '../data-display/GlassBadge.js';
import '../data-display/GlassBadgeLine.js';
import '../data-display/GlassDataGrid.js';
import '../data-display/GlassDataTable.js';
import '../data-display/GlassHeatmap.js';
import '../data-display/GlassLoadingSkeleton.js';
import '../data-display/GlassProgress.js';
import '../data-display/GlassTimeline.js';
import '../data-display/GlassSkeleton.js';
import '../data-display/GlassNotificationCenter.js';
import '../data-display/GlassAnimatedNumber.js';
import { GlassImageViewer } from './GlassImageViewer.js';
import { GlassCard } from '../card/GlassCard.js';
/**
* GlassGallery component
* A responsive image gallery with grid, masonry, and list layouts
*/
const GlassGallery = ({
images,
layout = "grid",
columns = 3,
aspectRatio = "square",
showInfo = true,
showActions = true,
enableLightbox = true,
showFilters = false,
enableSelection = false,
className,
onImageClick,
onSelectionChange,
...props
}) => {
const [lightboxIndex, setLightboxIndex] = useState(null);
const [selectedImages, setSelectedImages] = useState(new Set());
const [viewMode, setViewMode] = useState("grid");
const [filterCategory, setFilterCategory] = useState("all");
// Get aspect ratio classes
const getAspectRatioClass = useCallback(() => {
switch (aspectRatio) {
case "square":
return "aspect-square";
case "portrait":
return "aspect-[3/4]";
case "landscape":
return "aspect-[4/3]";
case "auto":
default:
return "aspect-auto";
}
}, [aspectRatio]);
// Get grid columns classes
const getGridColumnsClass = useCallback(() => {
const cols = Math.min(Math.max(columns, 1), 6);
return `grid-cols-1 sm:grid-cols-2 md:grid-cols-${Math.min(cols, 3)} lg:grid-cols-${Math.min(cols, 4)} xl:grid-cols-${cols}`;
}, [columns]);
// Handle image click
const handleImageClick = useCallback((image, index) => {
if (enableSelection) {
const newSelected = new Set(selectedImages);
if (newSelected.has(image.id)) {
newSelected.delete(image.id);
} else {
newSelected.add(image.id);
}
setSelectedImages(newSelected);
onSelectionChange?.(images.filter(img => newSelected.has(img.id)));
} else if (enableLightbox) {
setLightboxIndex(index);
}
onImageClick?.(image, index);
}, [enableSelection, enableLightbox, selectedImages, images, onImageClick, onSelectionChange]);
// Handle lightbox navigation
const handleLightboxNav = useCallback(direction => {
if (lightboxIndex === null) return;
let newIndex = lightboxIndex;
if (direction === "prev") {
newIndex = lightboxIndex > 0 ? lightboxIndex - 1 : images.length - 1;
} else {
newIndex = lightboxIndex < images.length - 1 ? lightboxIndex + 1 : 0;
}
setLightboxIndex(newIndex);
}, [lightboxIndex, images.length]);
// Filter images by category
const filteredImages = images.filter(image => filterCategory === "all" || image.category === filterCategory);
// Get unique categories
const categories = Array.from(new Set(images.map(img => img.category).filter(Boolean)));
// Grid Layout
const renderGridLayout = () => jsx("div", {
className: cn("grid glass-gap-4", getGridColumnsClass()),
children: filteredImages.map((image, index) => jsx("div", {
className: 'group relative animate-fade-in',
style: {
animationDelay: `${Math.min(index, 20) * 50}ms`,
animationFillMode: "both"
},
children: jsx(GlassCard, {
variant: "elevated",
interactive: true,
onClick: e => handleImageClick(image, index),
className: cn("overflow-hidden cursor-pointer hover:scale-[1.02] transition-all duration-200 glass-sheen glass-tilt", selectedImages.has(image.id) && "ring-2 ring-primary", getAspectRatioClass()),
children: jsxs(CardContent, {
className: "glass-p-0",
children: [jsxs("div", {
className: 'relative overflow-hidden',
children: [jsx("img", {
src: image.thumbnail || image.src,
alt: image.alt || image.title,
className: 'glass-w-full glass-h-full object-cover transition-transform duration-300 group-hover:scale-105',
loading: "lazy"
}), enableSelection && jsx("div", {
className: 'absolute glass-top-2 left-2',
children: jsx("div", {
className: cn("w-5 h-5 glass-radius-md border-2 transition-all", selectedImages.has(image.id) ? "bg-primary border-primary" : "bg-white/20 border-white/40"),
children: selectedImages.has(image.id) && jsx("div", {
className: "glass-w-full glass-h-full glass-flex glass-items-center glass-justify-center",
children: jsx("div", {
className: 'w-2 h-2 glass-surface-subtle glass-radius-full'
})
})
})
}), jsx("div", {
className: 'absolute inset-0 glass-surface-dark/0 group-hover:glass-surface-dark/40 transition-all duration-200',
children: jsx("div", {
className: 'absolute inset-0 glass-flex glass-items-center glass-justify-center opacity-0 group-hover:opacity-100 transition-opacity',
children: jsxs("div", {
className: "glass-flex glass-gap-2",
children: [jsx(GlassButton, {
variant: "secondary",
size: "sm",
onClick: e => {
e.stopPropagation();
if (enableLightbox) setLightboxIndex(index);
},
className: "glass-p-2",
children: jsx(ZoomIn, {
className: 'w-4 h-4'
})
}), showActions && jsxs(Fragment, {
children: [jsx(GlassButton, {
variant: "secondary",
size: "sm",
onClick: e => e.stopPropagation(),
className: "glass-p-2",
children: jsx(Heart, {
className: 'w-4 h-4'
})
}), jsx(GlassButton, {
variant: "secondary",
size: "sm",
onClick: e => e.stopPropagation(),
className: "glass-p-2",
children: jsx(Share2, {
className: 'w-4 h-4'
})
})]
})]
})
})
}), showInfo && image.title && jsxs("div", {
className: 'absolute bottom-0 left-0 right-0 glass-p-3 glass-gradient-primary glass-gradient-primary glass-gradient-primary',
children: [jsx("h3", {
className: 'text-primary font-medium glass-text-sm truncate',
children: image.title
}), image.description && jsx("p", {
className: 'text-primary/80 glass-text-xs glass-mt-1 line-clamp-2',
children: image.description
})]
})]
}), (image.likes || image.views || image.tags) && jsx("div", {
className: "glass-p-3 glass-border-t glass-border-white/10",
children: jsxs("div", {
className: 'glass-flex glass-items-center glass-justify-between glass-text-xs text-primary/60',
children: [jsxs("div", {
className: "glass-flex glass-items-center glass-gap-3",
children: [image.likes && jsxs("span", {
className: "glass-flex glass-items-center glass-gap-1",
children: [jsx(Heart, {
className: 'w-3 h-3'
}), image.likes]
}), image.views && jsxs("span", {
className: "glass-flex glass-items-center glass-gap-1",
children: [jsx(Eye, {
className: 'w-3 h-3'
}), image.views]
})]
}), image.tags && image.tags.length > 0 && jsx("div", {
className: "glass-flex glass-gap-1",
children: image.tags.slice(0, 2).map((tag, tagIndex) => jsx(GlassBadge, {
variant: "outline",
size: "sm",
children: tag
}, tagIndex))
})]
})
})]
})
})
}, image.id))
});
// List Layout
const renderListLayout = () => jsx("div", {
className: "glass-auto-gap glass-auto-gap-lg",
children: filteredImages.map((image, index) => jsx("div", {
className: 'animate-slide-in-up',
style: {
animationDelay: `${Math.min(index, 20) * 50}ms`,
animationFillMode: "both"
},
children: jsx(GlassCard, {
variant: "elevated",
interactive: true,
onClick: e => handleImageClick(image, index),
className: 'overflow-hidden cursor-pointer hover:scale-[1.01] transition-transform',
children: jsx(CardContent, {
className: "glass-p-0",
children: jsxs("div", {
className: "glass-flex",
children: [jsx("div", {
className: 'w-32 h-32 glass-flex-shrink-0',
children: jsx("img", {
src: image.thumbnail || image.src,
alt: image.alt || image.title,
className: 'glass-w-full glass-h-full object-cover',
loading: "lazy"
})
}), jsxs("div", {
className: "glass-flex-1 glass-p-4",
children: [jsxs("div", {
className: "glass-flex glass-items-start glass-justify-between",
children: [jsxs("div", {
className: "glass-flex-1",
children: [jsx("h3", {
className: 'text-primary font-medium mb-1',
children: image.title
}), image.description && jsx("p", {
className: 'text-primary/70 glass-text-sm mb-2',
children: image.description
}), jsxs("div", {
className: 'glass-flex glass-items-center glass-gap-4 glass-text-xs text-primary/60',
children: [image.likes && jsxs("span", {
className: "glass-flex glass-items-center glass-gap-1",
children: [jsx(Heart, {
className: 'w-3 h-3'
}), image.likes]
}), image.views && jsxs("span", {
className: "glass-flex glass-items-center glass-gap-1",
children: [jsx(Eye, {
className: 'w-3 h-3'
}), image.views]
}), image.createdAt && jsx("span", {
children: image.createdAt.toLocaleDateString()
})]
})]
}), showActions && jsxs("div", {
className: "glass-flex glass-gap-1 glass-ml-4",
children: [jsx(GlassButton, {
variant: "ghost",
size: "sm",
onClick: e => {
e.stopPropagation();
if (enableLightbox) setLightboxIndex(index);
},
className: "glass-p-2",
children: jsx(ZoomIn, {
className: 'w-4 h-4'
})
}), jsx(GlassButton, {
variant: "ghost",
size: "sm",
onClick: e => e.stopPropagation(),
className: "glass-p-2",
children: jsx(Heart, {
className: 'w-4 h-4'
})
}), jsx(GlassButton, {
variant: "ghost",
size: "sm",
onClick: e => e.stopPropagation(),
className: "glass-p-2",
children: jsx(Share2, {
className: 'w-4 h-4'
})
})]
})]
}), image.tags && image.tags.length > 0 && jsx("div", {
className: 'glass-flex glass-gap-1 mt-3',
children: image.tags.map((tag, tagIndex) => jsx(GlassBadge, {
variant: "outline",
size: "sm",
children: tag
}, tagIndex))
})]
})]
})
})
})
}, image.id))
});
// Lightbox Modal
const renderLightbox = () => {
if (lightboxIndex === null) return null;
const image = images[lightboxIndex];
return jsx("div", {
"data-glass-component": true,
className: 'fixed inset-0 z-50 glass-surface-dark/90 glass-flex glass-items-center glass-justify-center glass-p-4',
children: jsxs("div", {
className: 'relative max-w-4xl max-h-full',
children: [jsx(GlassButton, {
variant: "secondary",
size: "sm",
onClick: e => setLightboxIndex(null),
className: 'absolute top-4 right-4 z-10',
children: jsx(X, {
className: 'w-4 h-4'
})
}), images.length > 1 && jsxs(Fragment, {
children: [jsx(GlassButton, {
variant: "secondary",
size: "lg",
onClick: e => handleLightboxNav("prev"),
className: 'absolute left-4 glass-top-1/2 transform -translate-y-1/2',
children: jsx(ChevronLeft, {
className: 'w-6 h-6'
})
}), jsx(GlassButton, {
variant: "secondary",
size: "lg",
onClick: e => handleLightboxNav("next"),
className: 'absolute right-4 glass-top-1/2 transform -translate-y-1/2',
children: jsx(ChevronRight, {
className: 'w-6 h-6'
})
})]
}), jsx(GlassImageViewer, {
images: images.map(img => ({
src: img.src,
alt: img.alt,
title: img.title
})),
initialIndex: lightboxIndex,
enableZoom: true,
enablePan: true,
enableFullscreen: false,
onImageChange: setLightboxIndex,
className: 'max-h-[80vh]'
}), image && (image.title || image.description) && jsxs("div", {
className: 'glass-mt-4 text-center',
children: [image.title && jsx("h3", {
className: 'text-primary glass-text-lg font-medium mb-1',
children: image.title
}), image.description && jsx("p", {
className: 'text-primary/80',
children: image.description
})]
})]
})
});
};
return jsx(MotionFramer, {
preset: "fadeIn",
className: "glass-w-full",
children: jsxs("div", {
className: cn("glass-auto-gap glass-auto-gap-2xl", className),
...props,
children: [jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between",
children: [jsxs("div", {
className: "glass-flex glass-items-center glass-gap-4",
children: [showFilters && categories.length > 0 && jsxs("select", {
value: filterCategory,
onChange: e => setFilterCategory(e.target.value),
className: 'bg-glass-fill ring-1 ring-white/10 glass-radius-md glass-px-3 glass-py-1 glass-text-sm text-primary focus:outline-none focus:ring-white/30',
children: [jsx("option", {
value: "all",
children: "All Categories"
}), categories.map(category => jsx("option", {
value: category,
children: category
}, category))]
}), enableSelection && selectedImages.size > 0 && jsxs(GlassBadge, {
variant: "primary",
children: [selectedImages.size, " selected"]
})]
}), jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: [jsx(GlassButton, {
variant: viewMode === "grid" ? "primary" : "ghost",
size: "sm",
onClick: e => setViewMode("grid"),
className: "glass-p-2",
children: jsx(Grid3X3, {
className: 'w-4 h-4'
})
}), jsx(GlassButton, {
variant: viewMode === "list" ? "primary" : "ghost",
size: "sm",
onClick: e => setViewMode("list"),
className: "glass-p-2",
children: jsx(List, {
className: 'w-4 h-4'
})
})]
})]
}), filteredImages.length === 0 ? jsxs("div", {
className: 'text-center glass-py-12',
children: [jsx("div", {
className: 'text-primary/60 mb-4',
children: jsx(Grid3X3, {
className: 'w-12 h-12 glass-mx-auto'
})
}), jsx("p", {
className: 'text-primary/60',
children: filterCategory === "all" ? "No images to display" : `No images in ${filterCategory} category`
})]
}) : viewMode === "grid" ? renderGridLayout() : renderListLayout(), renderLightbox()]
})
});
};
export { GlassGallery, GlassGallery as default };
//# sourceMappingURL=GlassGallery.js.map