UNPKG

@grafana/ui

Version:
1 lines 13.7 kB
{"version":3,"file":"Carousel.mjs","sources":["../../../../src/components/Carousel/Carousel.tsx"],"sourcesContent":["import { css, cx } from '@emotion/css';\nimport { useDialog } from '@react-aria/dialog';\nimport { FocusScope } from '@react-aria/focus';\nimport { OverlayContainer, useOverlay } from '@react-aria/overlays';\nimport { useState, useEffect, useRef, useId } from 'react';\n\nimport { GrafanaTheme2 } from '@grafana/data';\nimport { t } from '@grafana/i18n';\n\nimport { useStyles2 } from '../../themes/ThemeContext';\nimport { Alert } from '../Alert/Alert';\nimport { clearButtonStyles } from '../Button/Button';\nimport { IconButton } from '../IconButton/IconButton';\n\n// Define the image item interface\nexport interface CarouselImage {\n path: string;\n name: string;\n}\n\nexport interface CarouselProps {\n images: CarouselImage[];\n}\n\n/**\n * The Carousel component displays a grid of image thumbnails that can be clicked to view full-sized images in a modal with navigation controls. It provides an elegant way to present collections of images or screenshots with fullscreen preview capabilities.\n *\n * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-carousel--docs\n */\nexport const Carousel: React.FC<CarouselProps> = ({ images }) => {\n const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n const [imageErrors, setImageErrors] = useState<Record<string, boolean>>({});\n const [validImages, setValidImages] = useState<CarouselImage[]>(images);\n const id = useId();\n\n const styles = useStyles2(getStyles);\n const resetButtonStyles = useStyles2(clearButtonStyles);\n\n const handleImageError = (path: string) => {\n setImageErrors((prev) => ({\n ...prev,\n [path]: true,\n }));\n };\n\n useEffect(() => {\n const filteredImages = images.filter((image) => !imageErrors[image.path]);\n setValidImages(filteredImages);\n }, [imageErrors, images]);\n\n const openPreview = (index: number) => {\n setSelectedIndex(index);\n };\n\n const closePreview = () => {\n setSelectedIndex(null);\n };\n\n const goToNext = () => {\n if (selectedIndex !== null && validImages.length > 0) {\n setSelectedIndex((selectedIndex + 1) % validImages.length);\n }\n };\n\n const goToPrevious = () => {\n if (selectedIndex !== null && validImages.length > 0) {\n setSelectedIndex((selectedIndex - 1 + validImages.length) % validImages.length);\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (selectedIndex === null) {\n return;\n }\n\n switch (event.key) {\n case 'ArrowRight':\n goToNext();\n break;\n case 'ArrowLeft':\n goToPrevious();\n break;\n case 'Escape':\n closePreview();\n break;\n default:\n break;\n }\n };\n\n const ref = useRef<HTMLDivElement>(null);\n\n const { overlayProps, underlayProps } = useOverlay({ isOpen: selectedIndex !== null, onClose: closePreview }, ref);\n const { dialogProps } = useDialog({}, ref);\n\n if (validImages.length === 0) {\n return (\n <Alert\n title={t('carousel.error', 'Something went wrong loading images')}\n severity=\"warning\"\n data-testid=\"alert-warning\"\n />\n );\n }\n\n return (\n <>\n <div className={cx(styles.imageGrid)}>\n {validImages.map((image, index) => {\n const imageNameId = `${id}-carousel-image-${index}`;\n return (\n <button\n aria-label={t('grafana-ui.carousel.aria-label-open-image', 'Open image preview')}\n aria-describedby={imageNameId}\n type=\"button\"\n key={image.path}\n onClick={() => openPreview(index)}\n className={cx(resetButtonStyles, styles.imageButton)}\n >\n <img src={image.path} alt=\"\" onError={() => handleImageError(image.path)} />\n <p id={imageNameId}>{image.name}</p>\n </button>\n );\n })}\n </div>\n\n {selectedIndex !== null && (\n <OverlayContainer>\n <div role=\"presentation\" className={styles.underlay} onClick={closePreview} {...underlayProps} />\n <FocusScope contain autoFocus restoreFocus>\n {/* convenience method for keyboard users */}\n {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}\n <div\n data-testid=\"carousel-full-screen\"\n ref={ref}\n {...overlayProps}\n {...dialogProps}\n onKeyDown={handleKeyDown}\n className={styles.overlay}\n >\n <IconButton\n name=\"times\"\n aria-label={t('carousel.close', 'Close')}\n size=\"xl\"\n onClick={closePreview}\n className={cx(styles.closeButton)}\n />\n\n <IconButton\n size=\"xl\"\n name=\"angle-left\"\n aria-label={t('carousel.previous', 'Previous')}\n onClick={goToPrevious}\n data-testid=\"previous-button\"\n />\n\n <div className={styles.imageContainer} data-testid=\"carousel-full-image\">\n <img\n className={styles.imagePreview}\n src={validImages[selectedIndex].path}\n alt={validImages[selectedIndex].name}\n onError={() => handleImageError(validImages[selectedIndex].path)}\n />\n </div>\n\n <IconButton\n size=\"xl\"\n name=\"angle-right\"\n aria-label={t('carousel.next', 'Next')}\n onClick={goToNext}\n data-testid=\"next-button\"\n />\n </div>\n </FocusScope>\n </OverlayContainer>\n )}\n </>\n );\n};\n\nconst getStyles = (theme: GrafanaTheme2) => ({\n imageButton: css({\n textAlign: 'left',\n }),\n imageContainer: css({\n display: 'flex',\n justifyContent: 'center',\n flex: 1,\n }),\n imagePreview: css({\n borderRadius: theme.shape.radius.lg,\n maxWidth: '100%',\n maxHeight: '80vh',\n objectFit: 'contain',\n }),\n imageGrid: css({\n display: 'grid',\n gridTemplateColumns: `repeat(auto-fill, minmax(200px, 1fr))`,\n gap: theme.spacing(2),\n marginBottom: '20px',\n\n '& img': {\n width: '100%',\n height: '150px',\n objectFit: 'cover',\n border: theme.colors.border.strong,\n borderRadius: theme.shape.radius.default,\n boxShadow: theme.shadows.z1,\n },\n '& p': {\n margin: theme.spacing(0.5, 0),\n fontWeight: theme.typography.fontWeightMedium,\n color: theme.colors.text.primary,\n },\n }),\n underlay: css({\n position: 'fixed',\n zIndex: theme.zIndex.modalBackdrop,\n inset: 0,\n backgroundColor: theme.components.overlay.background,\n }),\n overlay: css({\n alignItems: 'center',\n display: 'flex',\n gap: theme.spacing(1),\n height: 'fit-content',\n marginBottom: 'auto',\n marginTop: 'auto',\n padding: theme.spacing(2),\n position: 'fixed',\n inset: 0,\n zIndex: theme.zIndex.modal,\n }),\n closeButton: css({\n color: theme.colors.text.primary,\n position: 'fixed',\n top: theme.spacing(2),\n right: theme.spacing(2),\n }),\n});\n"],"names":[],"mappings":"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"}