UNPKG

@grafana/ui

Version:
220 lines (215 loc) • 7.19 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var jsxRuntime = require('react/jsx-runtime'); var css = require('@emotion/css'); var dialog = require('@react-aria/dialog'); var focus = require('@react-aria/focus'); var overlays = require('@react-aria/overlays'); var React = require('react'); var i18n = require('@grafana/i18n'); var ThemeContext = require('../../themes/ThemeContext.cjs'); var Alert = require('../Alert/Alert.cjs'); var Button = require('../Button/Button.cjs'); var IconButton = require('../IconButton/IconButton.cjs'); "use strict"; const Carousel = ({ images }) => { const [selectedIndex, setSelectedIndex] = React.useState(null); const [imageErrors, setImageErrors] = React.useState({}); const [validImages, setValidImages] = React.useState(images); const id = React.useId(); const styles = ThemeContext.useStyles2(getStyles); const resetButtonStyles = ThemeContext.useStyles2(Button.clearButtonStyles); const handleImageError = (path) => { setImageErrors((prev) => ({ ...prev, [path]: true })); }; React.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 = React.useRef(null); const { overlayProps, underlayProps } = overlays.useOverlay({ isOpen: selectedIndex !== null, onClose: closePreview }, ref); const { dialogProps } = dialog.useDialog({}, ref); if (validImages.length === 0) { return /* @__PURE__ */ jsxRuntime.jsx( Alert.Alert, { title: i18n.t("carousel.error", "Something went wrong loading images"), severity: "warning", "data-testid": "alert-warning" } ); } return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: css.cx(styles.imageGrid), children: validImages.map((image, index) => { const imageNameId = `${id}-carousel-image-${index}`; return /* @__PURE__ */ jsxRuntime.jsxs( "button", { "aria-label": i18n.t("grafana-ui.carousel.aria-label-open-image", "Open image preview"), "aria-describedby": imageNameId, type: "button", onClick: () => openPreview(index), className: css.cx(resetButtonStyles, styles.imageButton), children: [ /* @__PURE__ */ jsxRuntime.jsx("img", { src: image.path, alt: "", onError: () => handleImageError(image.path) }), /* @__PURE__ */ jsxRuntime.jsx("p", { id: imageNameId, children: image.name }) ] }, image.path ); }) }), selectedIndex !== null && /* @__PURE__ */ jsxRuntime.jsxs(overlays.OverlayContainer, { children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "presentation", className: styles.underlay, onClick: closePreview, ...underlayProps }), /* @__PURE__ */ jsxRuntime.jsx(focus.FocusScope, { contain: true, autoFocus: true, restoreFocus: true, children: /* @__PURE__ */ jsxRuntime.jsxs( "div", { "data-testid": "carousel-full-screen", ref, ...overlayProps, ...dialogProps, onKeyDown: handleKeyDown, className: styles.overlay, children: [ /* @__PURE__ */ jsxRuntime.jsx( IconButton.IconButton, { name: "times", "aria-label": i18n.t("carousel.close", "Close"), size: "xl", onClick: closePreview, className: css.cx(styles.closeButton) } ), /* @__PURE__ */ jsxRuntime.jsx( IconButton.IconButton, { size: "xl", name: "angle-left", "aria-label": i18n.t("carousel.previous", "Previous"), onClick: goToPrevious, "data-testid": "previous-button" } ), /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.imageContainer, "data-testid": "carousel-full-image", children: /* @__PURE__ */ jsxRuntime.jsx( "img", { className: styles.imagePreview, src: validImages[selectedIndex].path, alt: validImages[selectedIndex].name, onError: () => handleImageError(validImages[selectedIndex].path) } ) }), /* @__PURE__ */ jsxRuntime.jsx( IconButton.IconButton, { size: "xl", name: "angle-right", "aria-label": i18n.t("carousel.next", "Next"), onClick: goToNext, "data-testid": "next-button" } ) ] } ) }) ] }) ] }); }; const getStyles = (theme) => ({ imageButton: css.css({ textAlign: "left" }), imageContainer: css.css({ display: "flex", justifyContent: "center", flex: 1 }), imagePreview: css.css({ borderRadius: theme.shape.radius.lg, maxWidth: "100%", maxHeight: "80vh", objectFit: "contain" }), imageGrid: css.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.css({ position: "fixed", zIndex: theme.zIndex.modalBackdrop, inset: 0, backgroundColor: theme.components.overlay.background }), overlay: css.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.css({ color: theme.colors.text.primary, position: "fixed", top: theme.spacing(2), right: theme.spacing(2) }) }); exports.Carousel = Carousel; //# sourceMappingURL=Carousel.cjs.map