aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
183 lines (180 loc) • 6.1 kB
JavaScript
'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