UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

183 lines (180 loc) 6.1 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { forwardRef, useContext, useState, useCallback, useMemo } from 'react'; import { cn } from '../../lib/utilsComprehensive.js'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import { ImageListContext } from './ImageList.js'; import styles from './ImageListItem.module.css.js'; const elevationClassMap = { level1: styles.elevationLevel1, level2: styles.elevationLevel2, level3: styles.elevationLevel3, level4: styles.elevationLevel4 }; /** * ImageListItem Component Implementation */ function ImageListItemComponent(props, ref) { const { children, className, style, cols: propCols, rows: propRows, glass: propGlass, hoverOverlay = false, elevation = 0, rounded: propRounded, alt, src, srcSet, onClick, onKeyDown, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedBy, role, ...rest } = props; // Check if reduced motion is preferred const prefersReducedMotion = useReducedMotion(); const context = useContext(ImageListContext); const variant = context?.variant ?? 'standard'; const contextGlass = context?.glass ?? false; const variableSize = context?.variableSize ?? false; const contextRounded = context?.glassRadiusMd ?? false; // Calculate cols and rows based on variableSize const cols = propCols !== undefined ? propCols : // If variable size is enabled, allow items to span multiple columns/rows // Otherwise, enforce single-cell items variableSize ? 1 : 1; const rows = propRows !== undefined ? propRows : variableSize ? 1 : 1; // Merge props with context const glass = propGlass !== undefined ? propGlass : contextGlass; const glassRadiusMd = propRounded !== undefined ? propRounded : contextRounded; // Simplified animation settings const finalDisableAnimation = prefersReducedMotion; // State for hover and focus const [isFocused, setIsFocused] = useState(false); const [isHovered, setIsHovered] = useState(false); // Accessibility handlers const handleClick = useCallback(event => { onClick?.(event); }, [onClick]); const handleKeyDown = useCallback(event => { // Handle Enter and Space key activation if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); if (onClick) { onClick(event); } } // Pass through any custom key handling onKeyDown?.(event); }, [onClick, onKeyDown]); // Generate accessible description const accessibleDescription = useMemo(() => { if (alt) return alt; if (ariaLabel) return ariaLabel; return 'Image in gallery'; }, [alt, ariaLabel]); // Determine if this item is interactive const isInteractive = Boolean(onClick); // Generate ARIA attributes const ariaAttributes = useMemo(() => { const attributes = {}; if (isInteractive) { attributes.role = role || 'button'; attributes['aria-label'] = ariaLabel || accessibleDescription; if (ariaDescribedBy) { attributes['aria-describedby'] = ariaDescribedBy; } } else { attributes.role = role || 'listitem'; attributes['aria-label'] = ariaLabel || accessibleDescription; } // Add state information if (hoverOverlay && (isHovered || isFocused)) { attributes['aria-expanded'] = 'true'; } return attributes; }, [role, ariaLabel, ariaDescribedBy, accessibleDescription, isInteractive, hoverOverlay, isHovered, isFocused]); // Prepare image element if src is provided with enhanced accessibility const image = src ? jsx("img", { src: src, srcSet: srcSet, alt: alt || accessibleDescription, loading: "lazy", role: alt ? undefined : 'presentation', ...rest }) : null; const itemStyle = useMemo(() => { const styleVars = { '--image-item-col-span': String(Math.max(1, cols)), '--image-item-row-span': String(Math.max(1, rows)), '--image-item-scale': (!finalDisableAnimation && (isHovered || isFocused) ? 1.03 : 1).toString() }; if (finalDisableAnimation) { styleVars.transition = 'none'; } return { ...style, ...styleVars }; }, [cols, rows, finalDisableAnimation, isHovered, isFocused, style]); const elevationClass = useMemo(() => { if (glass) return undefined; if (typeof elevation === 'string') { return elevationClassMap[elevation]; } return undefined; }, [glass, elevation]); const rootClassName = cn(styles.item, glass && styles.glass, glassRadiusMd && styles.rounded, variant === 'masonry' && styles.masonryItem, isInteractive && styles.clickable, elevationClass, 'galileo-image-list-item', className); const overlayClassName = cn(styles.overlay, (isHovered || isFocused) && styles.overlayVisible, finalDisableAnimation && styles.noTransition); return jsx("li", { ref: ref, className: rootClassName, style: itemStyle, tabIndex: isInteractive ? 0 : -1, onClick: isInteractive ? handleClick : undefined, onKeyDown: isInteractive ? handleKeyDown : undefined, onMouseEnter: () => { setIsHovered(true); }, onMouseLeave: () => { setIsHovered(false); }, onFocus: () => { setIsFocused(true); }, onBlur: () => { setIsFocused(false); }, ...ariaAttributes, children: jsxs("div", { className: styles.media, children: [image, children, hoverOverlay && jsx("div", { className: overlayClassName, "aria-hidden": "true" })] }) }); } /** * ImageListItem Component * * An item component for the ImageList. */ const ImageListItem = /*#__PURE__*/forwardRef(ImageListItemComponent); /** * GlassImageListItem Component * * Glass variant of the ImageListItem component. */ const GlassImageListItem = /*#__PURE__*/forwardRef((props, ref) => jsx(ImageListItem, { ...props, glass: true, ref: ref })); GlassImageListItem.displayName = 'GlassImageListItem'; export { GlassImageListItem, ImageListItem, ImageListItem as default }; //# sourceMappingURL=ImageListItem.js.map