UNPKG

@grafana/ui

Version:
216 lines (213 loc) • 6.81 kB
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