react-advanced-gallery
Version:
A customizable React image gallery component with Bootstrap styling, fullscreen mode, animations, and support for various media types
930 lines (921 loc) • 34.9 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
FullscreenViewer: () => FullscreenViewer,
GalleryConfig: () => GalleryConfig,
GalleryGrid: () => GalleryGrid,
GalleryItem: () => GalleryItem,
ImageGallery: () => ImageGallery,
ImageUploader: () => ImageUploader,
useGallery: () => useGallery
});
module.exports = __toCommonJS(src_exports);
// src/hooks/useGallery.ts
var import_react = require("react");
var useGallery = ({
initialImages,
initialLayout = "grid",
initialAnimation = "fade",
initialThumbnailsPosition = "bottom",
initialEnableZoom = true
}) => {
const [images, setImages] = (0, import_react.useState)(initialImages);
const [selectedImage, setSelectedImage] = (0, import_react.useState)(null);
const [currentIndex, setCurrentIndex] = (0, import_react.useState)(0);
const [isFullscreenOpen, setIsFullscreenOpen] = (0, import_react.useState)(false);
const [zoomLevel, setZoomLevel] = (0, import_react.useState)(1);
const [layout, setLayout] = (0, import_react.useState)(initialLayout);
const [animation, setAnimation] = (0, import_react.useState)(initialAnimation);
const [thumbnailsPosition, setThumbnailsPosition] = (0, import_react.useState)(initialThumbnailsPosition);
const [enableZoom, setEnableZoom] = (0, import_react.useState)(initialEnableZoom);
(0, import_react.useEffect)(() => {
if (!isFullscreenOpen) {
setZoomLevel(1);
}
}, [isFullscreenOpen, selectedImage]);
const openFullscreen = (0, import_react.useCallback)((image) => {
const index = images.findIndex((img) => img.id === image.id);
setSelectedImage(image);
setCurrentIndex(index !== -1 ? index : 0);
setIsFullscreenOpen(true);
}, [images]);
const closeFullscreen = (0, import_react.useCallback)(() => {
setIsFullscreenOpen(false);
setSelectedImage(null);
}, []);
const toggleFavorite = (0, import_react.useCallback)((image) => {
setImages(
(prevImages) => prevImages.map(
(img) => img.id === image.id ? { ...img, isFavorite: !img.isFavorite } : img
)
);
if (selectedImage && selectedImage.id === image.id) {
setSelectedImage({ ...selectedImage, isFavorite: !selectedImage.isFavorite });
}
}, [selectedImage]);
const removeImage = (0, import_react.useCallback)((image) => {
const newImages = images.filter((img) => img.id !== image.id);
if (isFullscreenOpen && selectedImage && selectedImage.id === image.id) {
if (newImages.length > 0) {
const newIndex = currentIndex >= newImages.length ? newImages.length - 1 : currentIndex;
setCurrentIndex(newIndex);
setSelectedImage(newImages[newIndex]);
} else {
closeFullscreen();
}
}
setImages(newImages);
}, [isFullscreenOpen, selectedImage, images, currentIndex, closeFullscreen]);
const navigateToImage = (0, import_react.useCallback)((index) => {
if (index >= 0 && index < images.length) {
setCurrentIndex(index);
setSelectedImage(images[index]);
}
}, [images]);
const navigatePrev = (0, import_react.useCallback)(() => {
const newIndex = (currentIndex - 1 + images.length) % images.length;
navigateToImage(newIndex);
}, [currentIndex, images.length, navigateToImage]);
const navigateNext = (0, import_react.useCallback)(() => {
const newIndex = (currentIndex + 1) % images.length;
navigateToImage(newIndex);
}, [currentIndex, images.length, navigateToImage]);
const zoomIn = (0, import_react.useCallback)(() => {
setZoomLevel((prevZoom) => Math.min(prevZoom + 0.5, 3));
}, []);
const zoomOut = (0, import_react.useCallback)(() => {
setZoomLevel((prevZoom) => Math.max(prevZoom - 0.5, 0.5));
}, []);
const resetZoom = (0, import_react.useCallback)(() => {
setZoomLevel(1);
}, []);
const addImages = (0, import_react.useCallback)((newImages) => {
setImages((prevImages) => [...prevImages, ...newImages]);
}, []);
const updateConfig = (0, import_react.useCallback)((config) => {
if (config.layout)
setLayout(config.layout);
if (config.animation)
setAnimation(config.animation);
if (config.thumbnailsPosition)
setThumbnailsPosition(config.thumbnailsPosition);
if (config.enableZoom !== void 0)
setEnableZoom(config.enableZoom);
}, []);
(0, import_react.useEffect)(() => {
const handleKeyDown = (e) => {
if (!isFullscreenOpen)
return;
switch (e.key) {
case "Escape":
closeFullscreen();
break;
case "ArrowLeft":
navigatePrev();
break;
case "ArrowRight":
navigateNext();
break;
case "+":
zoomIn();
break;
case "-":
zoomOut();
break;
case "0":
resetZoom();
break;
default:
break;
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [isFullscreenOpen, closeFullscreen, navigatePrev, navigateNext, zoomIn, zoomOut, resetZoom]);
return {
images,
selectedImage,
currentIndex,
isFullscreenOpen,
zoomLevel,
layout,
animation,
thumbnailsPosition,
enableZoom,
openFullscreen,
closeFullscreen,
toggleFavorite,
removeImage,
navigateToImage,
navigatePrev,
navigateNext,
zoomIn,
zoomOut,
resetZoom,
addImages,
updateConfig
};
};
// src/components/GalleryConfig.tsx
var import_jsx_runtime = require("react/jsx-runtime");
function GalleryConfig({
layout,
animation,
thumbnailsPosition,
enableZoom,
onConfigChange
}) {
const handleLayoutChange = (value) => {
onConfigChange({ layout: value });
};
const handleAnimationChange = (value) => {
onConfigChange({ animation: value });
};
const handleThumbnailsPositionChange = (value) => {
onConfigChange({ thumbnailsPosition: value });
};
const handleZoomToggle = () => {
onConfigChange({ enableZoom: !enableZoom });
};
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rag-config rag-mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-header", children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "rag-config-title", children: "Gallery Configuration" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "rag-config-subtitle", children: "Customize your gallery experience" })
] }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-controls", children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-group", children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "layout-select", className: "rag-config-label", children: "Layout:" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
"select",
{
id: "layout-select",
value: layout,
onChange: (e) => handleLayoutChange(e.target.value),
className: "rag-config-select",
children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "grid", children: "Grid" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "masonry", children: "Masonry" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "carousel", children: "Carousel" })
]
}
)
] }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-group", children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "animation-select", className: "rag-config-label", children: "Animation:" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
"select",
{
id: "animation-select",
value: animation,
onChange: (e) => handleAnimationChange(e.target.value),
className: "rag-config-select",
children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "fade", children: "Fade" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "slide", children: "Slide" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "zoom", children: "Zoom" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "none", children: "None" })
]
}
)
] }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-group", children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "thumbnails-select", className: "rag-config-label", children: "Thumbnails:" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
"select",
{
id: "thumbnails-select",
value: thumbnailsPosition,
onChange: (e) => handleThumbnailsPositionChange(e.target.value),
className: "rag-config-select",
children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "bottom", children: "Bottom" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "top", children: "Top" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "left", children: "Left" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "right", children: "Right" })
]
}
)
] }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rag-config-group", children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "zoom-toggle", className: "rag-config-label", children: "Enable Zoom:" }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "rag-toggle", children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"input",
{
type: "checkbox",
id: "zoom-toggle",
checked: enableZoom,
onChange: handleZoomToggle,
className: "rag-toggle-input"
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rag-toggle-slider" })
] })
] })
] })
] }) });
}
// src/components/GalleryItem.tsx
var import_jsx_runtime2 = require("react/jsx-runtime");
function GalleryItem({
item,
onImageClick,
onFavoriteToggle,
onImageRemove,
animationType
}) {
const handleFavoriteClick = (e) => {
e.stopPropagation();
onFavoriteToggle(item);
};
const handleRemoveClick = (e) => {
e.stopPropagation();
onImageRemove(item);
};
const getAnimationClass = () => {
if (animationType === "none")
return "";
return `rag-${animationType}-enter`;
};
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
"div",
{
className: `rag-item ${getAnimationClass()}`,
onClick: () => onImageClick(item),
children: [
item.type === "image" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"img",
{
src: item.src,
alt: item.alt,
className: "rag-item-image",
loading: "lazy"
}
) : item.type === "video" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"img",
{
src: item.thumbnail,
alt: item.alt,
className: "rag-item-image",
loading: "lazy"
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rag-item-badge rag-item-badge-video", children: "Video" })
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"img",
{
src: item.thumbnail,
alt: item.alt,
className: "rag-item-image",
loading: "lazy"
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rag-item-badge rag-item-badge-external", children: "External" })
] }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rag-item-caption", children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "rag-item-title", children: item.title }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "rag-item-description", children: item.description })
] }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rag-item-actions", children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"button",
{
className: `rag-btn rag-btn-favorite ${item.isFavorite ? "active" : ""}`,
onClick: handleFavoriteClick,
"aria-label": item.isFavorite ? "Remove from favorites" : "Add to favorites",
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "rag-material-icons", children: item.isFavorite ? "star" : "star_border" })
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"button",
{
className: "rag-btn rag-btn-remove",
onClick: handleRemoveClick,
"aria-label": "Remove image",
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "rag-material-icons", children: "delete" })
}
)
] })
]
}
);
}
// src/components/GalleryGrid.tsx
var import_jsx_runtime3 = require("react/jsx-runtime");
function GalleryGrid({
items,
layout,
animation,
onImageClick,
onFavoriteToggle,
onImageRemove
}) {
if (layout === "grid") {
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-grid rag-mb-4", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
GalleryItem,
{
item,
onImageClick,
onFavoriteToggle,
onImageRemove,
animationType: animation
},
item.id
)) });
}
if (layout === "masonry") {
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "rag-masonry rag-mb-4", children: [
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-d-flex rag-flex-column rag-gap-3", children: items.filter((_, i) => i % 3 === 0).map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
GalleryItem,
{
item,
onImageClick,
onFavoriteToggle,
onImageRemove,
animationType: animation
},
item.id
)) }),
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-d-flex rag-flex-column rag-gap-3", children: items.filter((_, i) => i % 3 === 1).map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
GalleryItem,
{
item,
onImageClick,
onFavoriteToggle,
onImageRemove,
animationType: animation
},
item.id
)) }),
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-d-flex rag-flex-column rag-gap-3", style: { display: window.innerWidth < 992 ? "none" : "flex" }, children: items.filter((_, i) => i % 3 === 2).map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
GalleryItem,
{
item,
onImageClick,
onFavoriteToggle,
onImageRemove,
animationType: animation
},
item.id
)) })
] });
}
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-carousel-container rag-mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rag-carousel", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
"div",
{
className: "rag-carousel-item",
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
GalleryItem,
{
item,
onImageClick,
onFavoriteToggle,
onImageRemove,
animationType: animation
}
)
},
item.id
)) }) });
}
// src/components/FullscreenViewer.tsx
var import_react2 = require("react");
var import_jsx_runtime4 = require("react/jsx-runtime");
function FullscreenViewer({
isOpen,
currentImage,
images,
currentIndex,
zoomLevel,
onClose,
onPrev,
onNext,
onFavoriteToggle,
onImageRemove,
onZoomIn,
onZoomOut,
onThumbnailClick
}) {
const [dragPosition, setDragPosition] = (0, import_react2.useState)({ x: 0, y: 0 });
const containerRef = (0, import_react2.useRef)(null);
(0, import_react2.useEffect)(() => {
setDragPosition({ x: 0, y: 0 });
}, [currentIndex, zoomLevel]);
const handleDownload = () => {
if (!currentImage)
return;
const link = document.createElement("a");
link.href = currentImage.src;
link.download = currentImage.title || "image";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
const handleFullScreen = () => {
if (!containerRef.current)
return;
if (!document.fullscreenElement) {
containerRef.current.requestFullscreen().catch((err) => {
console.error(`Error attempting to enable fullscreen: ${err.message}`);
});
} else {
document.exitFullscreen();
}
};
const dragConstraints = zoomLevel > 1 ? {
top: -((containerRef.current?.clientHeight || 0) * (zoomLevel - 1) / 2),
left: -((containerRef.current?.clientWidth || 0) * (zoomLevel - 1) / 2),
right: (containerRef.current?.clientWidth || 0) * (zoomLevel - 1) / 2,
bottom: (containerRef.current?.clientHeight || 0) * (zoomLevel - 1) / 2
} : { top: 0, left: 0, right: 0, bottom: 0 };
if (!isOpen || !currentImage)
return null;
const formatDate = (dateString) => {
if (!dateString)
return "";
try {
return new Date(dateString).toLocaleDateString();
} catch (e) {
return "";
}
};
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
"div",
{
ref: containerRef,
className: "rag-fullscreen",
children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-header", children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-title", children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"button",
{
className: "rag-fullscreen-btn",
onClick: onClose,
"aria-label": "Close fullscreen view",
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "close" })
}
),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: currentImage.title })
] }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-actions", children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"button",
{
className: "rag-fullscreen-btn",
onClick: () => onFavoriteToggle(currentImage),
"aria-label": currentImage.isFavorite ? "Remove from favorites" : "Add to favorites",
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `rag-material-icons ${currentImage.isFavorite ? "active" : ""}`, children: currentImage.isFavorite ? "star" : "star_border" })
}
),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"button",
{
className: "rag-fullscreen-btn",
onClick: () => onImageRemove(currentImage),
"aria-label": "Delete image",
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "delete" })
}
),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"button",
{
className: "rag-fullscreen-btn",
onClick: handleDownload,
"aria-label": "Download image",
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "download" })
}
)
] })
] }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-content", children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"button",
{
className: "rag-fullscreen-nav-prev",
onClick: onPrev,
"aria-label": "Previous image",
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "chevron_left" })
}
),
currentImage.type === "video" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"video",
{
src: currentImage.src,
poster: currentImage.thumbnail,
controls: true,
className: "rag-fullscreen-image"
}
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"img",
{
src: currentImage.src,
alt: currentImage.alt,
className: `rag-fullscreen-image ${zoomLevel > 1 ? "zoomed" : ""}`,
style: {
transform: `scale(${zoomLevel})`,
position: "relative",
left: `${dragPosition.x}px`,
top: `${dragPosition.y}px`
},
draggable: false,
onMouseDown: (e) => {
if (zoomLevel <= 1)
return;
const startX = e.clientX;
const startY = e.clientY;
const startDragX = dragPosition.x;
const startDragY = dragPosition.y;
const onMouseMove = (e2) => {
const dx = e2.clientX - startX;
const dy = e2.clientY - startY;
const newX = Math.min(Math.max(startDragX + dx, -dragConstraints.right), dragConstraints.right);
const newY = Math.min(Math.max(startDragY + dy, -dragConstraints.bottom), dragConstraints.bottom);
setDragPosition({ x: newX, y: newY });
};
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
}
}
),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"button",
{
className: "rag-fullscreen-nav-next",
onClick: onNext,
"aria-label": "Next image",
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "chevron_right" })
}
),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-zoom-controls", children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"button",
{
className: "rag-fullscreen-btn",
onClick: onZoomOut,
"aria-label": "Zoom out",
disabled: zoomLevel <= 0.5,
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "zoom_out" })
}
),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"button",
{
className: "rag-fullscreen-btn",
onClick: onZoomIn,
"aria-label": "Zoom in",
disabled: zoomLevel >= 3,
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "zoom_in" })
}
),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"button",
{
className: "rag-fullscreen-btn",
onClick: handleFullScreen,
"aria-label": "Toggle fullscreen",
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "rag-material-icons", children: "fullscreen" })
}
)
] })
] }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-footer", children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "rag-fullscreen-thumbnails", children: images.map((image, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"div",
{
className: `rag-fullscreen-thumbnail ${index === currentIndex ? "active" : ""}`,
onClick: () => onThumbnailClick(index),
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"img",
{
src: image.thumbnail,
alt: `${image.alt} thumbnail`,
loading: "lazy"
}
)
},
image.id
)) }),
currentImage.metadata && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "rag-fullscreen-metadata", children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: [
currentImage.metadata.camera && `Shot on ${currentImage.metadata.camera}`,
currentImage.metadata.aperture,
currentImage.metadata.shutterSpeed,
currentImage.metadata.iso && `ISO ${currentImage.metadata.iso}`,
currentImage.metadata.focalLength
].filter(Boolean).join(" \u2022 ") }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: [
currentImage.metadata.dateTaken && `Uploaded ${formatDate(currentImage.metadata.dateTaken)}`,
currentImage.metadata.fileSize,
currentImage.metadata.dimensions
].filter(Boolean).join(" \u2022 ") })
] })
] })
]
}
);
}
// src/components/ImageUploader.tsx
var import_react3 = require("react");
var import_jsx_runtime5 = require("react/jsx-runtime");
function ImageUploader({ onImageUpload }) {
const [isDragging, setIsDragging] = (0, import_react3.useState)(false);
const fileInputRef = (0, import_react3.useRef)(null);
const validateFile = (file) => {
const validTypes = ["image/jpeg", "image/png", "image/gif", "image/webp", "video/mp4"];
if (!validTypes.includes(file.type)) {
console.error("Invalid file type. Only JPG, PNG, GIF, WebP, and MP4 files are supported.");
return false;
}
const maxSize = 10 * 1024 * 1024;
if (file.size > maxSize) {
console.error("File too large. Maximum file size is 10MB.");
return false;
}
return true;
};
const handleFiles = (files) => {
const validFiles = Array.from(files).filter(validateFile);
if (validFiles.length > 0) {
onImageUpload(validFiles);
console.log(`Successfully added ${validFiles.length} file(s).`);
}
};
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
};
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
handleFiles(e.dataTransfer.files);
}
};
const handleFileInputChange = (e) => {
if (e.target.files && e.target.files.length > 0) {
handleFiles(e.target.files);
e.target.value = "";
}
};
const handleButtonClick = () => {
if (fileInputRef.current) {
fileInputRef.current.click();
}
};
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rag-uploader", children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "rag-uploader-title", children: "Add New Images" }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
"div",
{
className: `rag-dropzone ${isDragging ? "active" : ""}`,
onDragEnter: handleDragEnter,
onDragLeave: handleDragLeave,
onDragOver: handleDragOver,
onDrop: handleDrop,
children: [
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "rag-material-icons rag-dropzone-icon", children: "add_photo_alternate" }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "rag-dropzone-text", children: isDragging ? "Drop files here" : "Drag and drop images here, or click to browse" }),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
"input",
{
type: "file",
ref: fileInputRef,
className: "hidden",
multiple: true,
accept: "image/jpeg,image/png,image/gif,image/webp,video/mp4",
onChange: handleFileInputChange
}
),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
"button",
{
className: "rag-upload-btn",
onClick: handleButtonClick,
children: "Select Files"
}
),
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "rag-upload-hint", children: "Supports JPG, PNG, GIF, WebP, and MP4 up to 10MB" })
]
}
)
] });
}
// src/components/ImageGallery.tsx
var import_jsx_runtime6 = require("react/jsx-runtime");
var generateId = () => `${Date.now()}-${Math.floor(Math.random() * 1e3)}`;
function ImageGallery({
images,
layout = "grid",
animation = "fade",
thumbnailsPosition = "bottom",
enableZoom = true,
onImageClick,
onFavoriteToggle,
onImageRemove,
onImageUpload
}) {
const {
images: galleryImages,
selectedImage,
currentIndex,
isFullscreenOpen,
zoomLevel,
layout: activeLayout,
animation: activeAnimation,
thumbnailsPosition: activeThumbnailsPosition,
enableZoom: activeEnableZoom,
openFullscreen,
closeFullscreen,
toggleFavorite,
removeImage,
navigateToImage,
navigatePrev,
navigateNext,
zoomIn,
zoomOut,
addImages,
updateConfig
} = useGallery({
initialImages: images,
initialLayout: layout,
initialAnimation: animation,
initialThumbnailsPosition: thumbnailsPosition,
initialEnableZoom: enableZoom
});
const handleImageClick = (image) => {
openFullscreen(image);
if (onImageClick) {
onImageClick(image);
}
};
const handleFavoriteToggle = (image) => {
toggleFavorite(image);
if (onFavoriteToggle) {
onFavoriteToggle(image);
}
};
const handleImageRemove = (image) => {
removeImage(image);
if (onImageRemove) {
onImageRemove(image);
}
};
const handleImageUpload = (files) => {
const newImages = files.map((file) => {
const isVideo = file.type.startsWith("video/");
const url = URL.createObjectURL(file);
return {
id: generateId(),
src: url,
thumbnail: url,
alt: file.name,
title: file.name.split(".").slice(0, -1).join("."),
description: isVideo ? "Video file" : "Image file",
type: isVideo ? "video" : "image",
isFavorite: false,
metadata: {
fileSize: `${(file.size / (1024 * 1024)).toFixed(1)} MB`,
dateTaken: (/* @__PURE__ */ new Date()).toISOString()
}
};
});
addImages(newImages);
if (onImageUpload) {
onImageUpload(files);
}
};
const handleConfigChange = (newConfig) => {
updateConfig(newConfig);
};
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rag-container", children: [
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
GalleryConfig,
{
layout: activeLayout,
animation: activeAnimation,
thumbnailsPosition: activeThumbnailsPosition,
enableZoom: activeEnableZoom,
onConfigChange: handleConfigChange
}
),
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
GalleryGrid,
{
items: galleryImages,
layout: activeLayout,
animation: activeAnimation,
onImageClick: handleImageClick,
onFavoriteToggle: handleFavoriteToggle,
onImageRemove: handleImageRemove
}
),
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ImageUploader, { onImageUpload: handleImageUpload }),
isFullscreenOpen && selectedImage && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
FullscreenViewer,
{
isOpen: isFullscreenOpen,
currentImage: selectedImage,
images: galleryImages,
currentIndex,
zoomLevel,
onClose: closeFullscreen,
onPrev: navigatePrev,
onNext: navigateNext,
onFavoriteToggle: handleFavoriteToggle,
onImageRemove: handleImageRemove,
onZoomIn: zoomIn,
onZoomOut: zoomOut,
onThumbnailClick: navigateToImage
}
)
] });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FullscreenViewer,
GalleryConfig,
GalleryGrid,
GalleryItem,
ImageGallery,
ImageUploader,
useGallery
});