aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
353 lines (350 loc) • 13 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { AnimatePresence, motion } from 'framer-motion';
import { forwardRef, useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { useMotionPreference } from '../../hooks/useMotionPreference.js';
import '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import '../../primitives/motion/MotionFramer.js';
import { useA11yId } from '../../utils/a11y.js';
import { useGlassSound } from '../../utils/soundDesign.js';
const GlassGoldenRatioGrid = /*#__PURE__*/forwardRef(({
// TODO: Integrate ContrastGuard for any section titles, labels, and helper text for WCAG AA compliance
items = [],
containerWidth = 800,
containerHeight = 600,
goldenRatio = 1.618,
subdivisionLevels = 4,
spacing = 8,
animateLayout = true,
showGrid = false,
showRatioLines = true,
responsive = true,
onItemClick,
onItemHover,
onLayoutChange,
glassConfig = {},
soundEnabled = true,
className = "",
style = {},
...props
}, ref) => {
const [hoveredItem, setHoveredItem] = useState(null);
const [selectedItem, setSelectedItem] = useState(null);
const [dimensions, setDimensions] = useState({
width: containerWidth,
height: containerHeight
});
const containerRef = useRef(null);
const {
prefersReducedMotion
} = useMotionPreference();
const gridId = useA11yId();
const {
play
} = useGlassSound();
// Responsive dimension handling
useEffect(() => {
if (!responsive || !containerRef.current) return;
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
const {
width,
height
} = entry.contentRect;
setDimensions({
width,
height
});
}
});
resizeObserver.observe(containerRef.current);
return () => resizeObserver.disconnect();
}, [responsive]);
// Generate golden ratio subdivisions
const generateGoldenSections = useCallback(() => {
const sections = [];
const queue = [{
x: 0,
y: 0,
width: dimensions.width,
height: dimensions.height,
level: 0
}];
while (queue.length > 0 && sections.length < Math.pow(2, subdivisionLevels)) {
const current = queue.shift();
if (current.level >= subdivisionLevels) {
sections.push({
...current,
ratio: current.width / current.height
});
continue;
}
// Apply golden ratio subdivision
const isHorizontalSplit = current.width > current.height;
if (isHorizontalSplit) {
// Split horizontally using golden ratio
const majorWidth = current.width / goldenRatio;
const minorWidth = current.width - majorWidth;
queue.push({
x: current.x,
y: current.y,
width: majorWidth,
height: current.height,
level: current.level + 1
});
queue.push({
x: current.x + majorWidth,
y: current.y,
width: minorWidth,
height: current.height,
level: current.level + 1
});
} else {
// Split vertically using golden ratio
const majorHeight = current.height / goldenRatio;
const minorHeight = current.height - majorHeight;
queue.push({
x: current.x,
y: current.y,
width: current.width,
height: majorHeight,
level: current.level + 1
});
queue.push({
x: current.x,
y: current.y + majorHeight,
width: current.width,
height: minorHeight,
level: current.level + 1
});
}
}
return sections;
}, [dimensions, goldenRatio, subdivisionLevels]);
// Assign items to sections based on priority and size requirements
const assignItemsToSections = useCallback(sections => {
const sortedItems = [...items].sort((a, b) => (b.priority || 0) - (a.priority || 0));
const sortedSections = [...sections].sort((a, b) => b.width * b.height - a.width * a.height);
const assignedSections = sortedSections.map((section, index) => {
const item = sortedItems[index];
return {
...section,
item: item || undefined
};
});
return assignedSections;
}, [items]);
const sections = useMemo(() => {
const goldenSections = generateGoldenSections();
const assignedSections = assignItemsToSections(goldenSections);
// Notify about layout changes
onLayoutChange?.(assignedSections);
return assignedSections;
}, [generateGoldenSections, assignItemsToSections, onLayoutChange]);
// Generate spiral golden ratio curve points
const generateGoldenSpiral = useCallback(() => {
const points = [];
const centerX = dimensions.width / 2;
const centerY = dimensions.height / 2;
const maxRadius = Math.min(dimensions.width, dimensions.height) / 3;
for (let i = 0; i < 200; i++) {
const angle = i * 0.2;
const radius = maxRadius / 20 * Math.pow(goldenRatio, angle / (Math.PI * 2));
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
if (x >= 0 && x <= dimensions.width && y >= 0 && y <= dimensions.height) {
points.push({
x,
y
});
}
}
return points;
}, [dimensions, goldenRatio]);
const spiralPoints = useMemo(() => generateGoldenSpiral(), [generateGoldenSpiral]);
const handleItemClick = useCallback(item => {
setSelectedItem(item.id);
onItemClick?.(item);
if (soundEnabled) {
play("click");
}
}, [onItemClick, soundEnabled, play]);
const handleItemHover = useCallback(item => {
setHoveredItem(item?.id || null);
onItemHover?.(item);
if (soundEnabled && item) {
play("hover");
}
}, [onItemHover, soundEnabled, play]);
const getItemVariants = () => ({
hidden: {
scale: 0,
opacity: 0
},
visible: delay => ({
scale: 1,
opacity: 1,
transition: {
type: "spring",
tension: 300,
friction: 25,
delay: prefersReducedMotion ? 0 : delay * 0.05
}
}),
hover: {
scale: 1.02,
transition: {
type: "spring",
tension: 400,
friction: 20
}
},
selected: {
scale: 1.05,
transition: {
type: "spring",
tension: 400,
friction: 20
}
}
});
return jsxs(OptimizedGlassCore, {
ref: ref,
className: `glass-golden-ratio-grid relative overflow-hidden ${className}`,
style: {
width: responsive ? "100%" : containerWidth,
height: responsive ? "100%" : containerHeight,
minHeight: containerHeight,
...style
},
glassConfig: {
blur: 12,
opacity: 0.95,
saturation: 1.05,
brightness: 1.02,
...glassConfig
},
role: "grid",
"aria-label": "Golden ratio layout grid",
id: gridId,
...props,
children: [jsxs("div", {
ref: containerRef,
className: 'absolute inset-0',
style: {
width: dimensions.width,
height: dimensions.height
},
children: [showRatioLines && jsx("svg", {
className: 'absolute inset-0 pointer-events-none',
width: dimensions.width,
height: dimensions.height,
children: jsx("path", {
d: `M ${spiralPoints.map(p => `${p.x},${p.y}`).join(" L ")}`,
stroke: "rgba(255, 215, 0, 0.3)",
strokeWidth: "2",
fill: "none",
strokeDasharray: "5,5"
})
}), showGrid && jsx("svg", {
className: 'absolute inset-0 pointer-events-none',
width: dimensions.width,
height: dimensions.height,
children: sections.map((section, index) => jsx("rect", {
x: section.x,
y: section.y,
width: section.width,
height: section.height,
stroke: "rgba(var(--glass-color-white) / var(--glass-opacity-20))",
strokeWidth: "1",
fill: "none",
strokeDasharray: "3,3"
}, `grid-${index}`))
}), jsx(AnimatePresence, {
children: sections.map((section, index) => {
const hasItem = !!section.item;
const isHovered = hasItem && hoveredItem === section.item.id;
const isSelected = hasItem && selectedItem === section.item.id;
return jsxs(motion.div, {
className: `
absolute overflow-hidden
${hasItem ? "cursor-pointer" : "cursor-default"}
`,
style: {
left: section.x + spacing / 2,
top: section.y + spacing / 2,
width: section.width - spacing,
height: section.height - spacing
},
custom: index,
variants: getItemVariants(),
initial: "hidden",
animate: isSelected ? "selected" : isHovered ? "hover" : "visible",
exit: "hidden",
onMouseEnter: () => hasItem && handleItemHover(section.item),
onMouseLeave: () => handleItemHover(null),
onClick: () => hasItem && handleItemClick(section.item),
children: [hasItem ? jsx("div", {
className: `
w-full h-full glass-surface rounded-lg border border-white/20
glass-backdrop-blur-md transition-all duration-200 p-3
flex items-center justify-center text-center
${isHovered || isSelected ? "bg-white/20 border-white/40" : "bg-white/10 border-white/20"}
`,
children: section.item.content
}) : jsx("div", {
className: 'glass-w-full glass-h-full glass-border glass-border-dashed glass-border-white/10 glass-radius-lg glass-flex glass-items-center glass-justify-center text-primary/30',
children: jsx("div", {
className: "glass-text-xs",
children: "Empty"
})
}), (showGrid || isHovered) && jsx("div", {
className: 'absolute glass-top-1 left-1 glass-surface-dark/50 text-primary glass-text-xs glass-px-1 glass-py-0.5 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard',
children: section.ratio.toFixed(2)
}), Math.abs(section.ratio - goldenRatio) < 0.1 && jsx("div", {
className: 'absolute glass-top-1 right-1 w-2 h-2 glass-surface-yellow glass-radius-full opacity-70'
})]
}, `section-${index}`);
})
})]
}), jsxs("div", {
className: 'absolute bottom-4 left-4 glass-flex glass-flex-col glass-gap-1 glass-text-xs text-primary/70',
children: [jsxs("div", {
className: "glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard",
children: ["Sections: ", sections.length]
}), jsxs("div", {
className: "glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard",
children: ["Items: ", items.length]
}), jsxs("div", {
className: "glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard",
children: ["Golden Ratio: ", goldenRatio]
}), jsxs("div", {
className: "glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard",
children: ["Levels: ", subdivisionLevels]
})]
}), jsxs("div", {
className: 'absolute top-4 right-4 glass-flex glass-flex-col glass-gap-1 glass-text-xs text-primary/70',
children: [jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2 glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard",
children: [jsx("div", {
className: 'w-2 h-2 glass-surface-yellow glass-radius-full'
}), "Golden Ratio"]
}), showRatioLines && jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2 glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard",
children: [jsx("div", {
className: 'w-4 h-0-5 glass-surface-yellow opacity-50',
style: {
background: "repeating-linear-gradient(to right, var(--glass-color-warning-light) 0, var(--glass-color-warning-light) 3px, transparent 3px, transparent 6px)"
}
}), "Spiral"]
})]
})]
});
});
GlassGoldenRatioGrid.displayName = "GlassGoldenRatioGrid";
export { GlassGoldenRatioGrid };
//# sourceMappingURL=GlassGoldenRatioGrid.js.map