@grafana/ui
Version:
Grafana Components Library
216 lines (213 loc) • 6.81 kB
JavaScript
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { css, cx } from '@emotion/css';
import { useDialog } from '@react-aria/dialog';
import { FocusScope } from '@react-aria/focus';
import { useOverlay, OverlayContainer } from '@react-aria/overlays';
import { useState, useId, useEffect, useRef } from 'react';
import { t } from '@grafana/i18n';
import { useStyles2 } from '../../themes/ThemeContext.mjs';
import { Alert } from '../Alert/Alert.mjs';
import { clearButtonStyles } from '../Button/Button.mjs';
import { IconButton } from '../IconButton/IconButton.mjs';
"use strict";
const Carousel = ({ images }) => {
const [selectedIndex, setSelectedIndex] = useState(null);
const [imageErrors, setImageErrors] = useState({});
const [validImages, setValidImages] = useState(images);
const id = useId();
const styles = useStyles2(getStyles);
const resetButtonStyles = useStyles2(clearButtonStyles);
const handleImageError = (path) => {
setImageErrors((prev) => ({
...prev,
[path]: true
}));
};
useEffect(() => {
const filteredImages = images.filter((image) => !imageErrors[image.path]);
setValidImages(filteredImages);
}, [imageErrors, images]);
const openPreview = (index) => {
setSelectedIndex(index);
};
const closePreview = () => {
setSelectedIndex(null);
};
const goToNext = () => {
if (selectedIndex !== null && validImages.length > 0) {
setSelectedIndex((selectedIndex + 1) % validImages.length);
}
};
const goToPrevious = () => {
if (selectedIndex !== null && validImages.length > 0) {
setSelectedIndex((selectedIndex - 1 + validImages.length) % validImages.length);
}
};
const handleKeyDown = (event) => {
if (selectedIndex === null) {
return;
}
switch (event.key) {
case "ArrowRight":
goToNext();
break;
case "ArrowLeft":
goToPrevious();
break;
case "Escape":
closePreview();
break;
default:
break;
}
};
const ref = useRef(null);
const { overlayProps, underlayProps } = useOverlay({ isOpen: selectedIndex !== null, onClose: closePreview }, ref);
const { dialogProps } = useDialog({}, ref);
if (validImages.length === 0) {
return /* @__PURE__ */ jsx(
Alert,
{
title: t("carousel.error", "Something went wrong loading images"),
severity: "warning",
"data-testid": "alert-warning"
}
);
}
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", { className: cx(styles.imageGrid), children: validImages.map((image, index) => {
const imageNameId = `${id}-carousel-image-${index}`;
return /* @__PURE__ */ jsxs(
"button",
{
"aria-label": t("grafana-ui.carousel.aria-label-open-image", "Open image preview"),
"aria-describedby": imageNameId,
type: "button",
onClick: () => openPreview(index),
className: cx(resetButtonStyles, styles.imageButton),
children: [
/* @__PURE__ */ jsx("img", { src: image.path, alt: "", onError: () => handleImageError(image.path) }),
/* @__PURE__ */ jsx("p", { id: imageNameId, children: image.name })
]
},
image.path
);
}) }),
selectedIndex !== null && /* @__PURE__ */ jsxs(OverlayContainer, { children: [
/* @__PURE__ */ jsx("div", { role: "presentation", className: styles.underlay, onClick: closePreview, ...underlayProps }),
/* @__PURE__ */ jsx(FocusScope, { contain: true, autoFocus: true, restoreFocus: true, children: /* @__PURE__ */ jsxs(
"div",
{
"data-testid": "carousel-full-screen",
ref,
...overlayProps,
...dialogProps,
onKeyDown: handleKeyDown,
className: styles.overlay,
children: [
/* @__PURE__ */ jsx(
IconButton,
{
name: "times",
"aria-label": t("carousel.close", "Close"),
size: "xl",
onClick: closePreview,
className: cx(styles.closeButton)
}
),
/* @__PURE__ */ jsx(
IconButton,
{
size: "xl",
name: "angle-left",
"aria-label": t("carousel.previous", "Previous"),
onClick: goToPrevious,
"data-testid": "previous-button"
}
),
/* @__PURE__ */ jsx("div", { className: styles.imageContainer, "data-testid": "carousel-full-image", children: /* @__PURE__ */ jsx(
"img",
{
className: styles.imagePreview,
src: validImages[selectedIndex].path,
alt: validImages[selectedIndex].name,
onError: () => handleImageError(validImages[selectedIndex].path)
}
) }),
/* @__PURE__ */ jsx(
IconButton,
{
size: "xl",
name: "angle-right",
"aria-label": t("carousel.next", "Next"),
onClick: goToNext,
"data-testid": "next-button"
}
)
]
}
) })
] })
] });
};
const getStyles = (theme) => ({
imageButton: css({
textAlign: "left"
}),
imageContainer: css({
display: "flex",
justifyContent: "center",
flex: 1
}),
imagePreview: css({
borderRadius: theme.shape.radius.lg,
maxWidth: "100%",
maxHeight: "80vh",
objectFit: "contain"
}),
imageGrid: css({
display: "grid",
gridTemplateColumns: `repeat(auto-fill, minmax(200px, 1fr))`,
gap: theme.spacing(2),
marginBottom: "20px",
"& img": {
width: "100%",
height: "150px",
objectFit: "cover",
border: theme.colors.border.strong,
borderRadius: theme.shape.radius.default,
boxShadow: theme.shadows.z1
},
"& p": {
margin: theme.spacing(0.5, 0),
fontWeight: theme.typography.fontWeightMedium,
color: theme.colors.text.primary
}
}),
underlay: css({
position: "fixed",
zIndex: theme.zIndex.modalBackdrop,
inset: 0,
backgroundColor: theme.components.overlay.background
}),
overlay: css({
alignItems: "center",
display: "flex",
gap: theme.spacing(1),
height: "fit-content",
marginBottom: "auto",
marginTop: "auto",
padding: theme.spacing(2),
position: "fixed",
inset: 0,
zIndex: theme.zIndex.modal
}),
closeButton: css({
color: theme.colors.text.primary,
position: "fixed",
top: theme.spacing(2),
right: theme.spacing(2)
})
});
export { Carousel };
//# sourceMappingURL=Carousel.mjs.map