UNPKG

aura-glass

Version:

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

319 lines (316 loc) 11.8 kB
'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 GlassFractalLayout = /*#__PURE__*/forwardRef(({ // TODO: Integrate ContrastGuard for any section titles, labels, and helper text for WCAG AA compliance nodes = [], maxDepth = 5, fractalType = "tree", scaleFactor = 0.618, // Golden ratio branchAngle = 30, initialScale = 1, recursive = true, animateGrowth = true, zoomLevel = 1, centerNode = true, interactiveZoom = true, onNodeClick, onNodeHover, glassConfig = {}, soundEnabled = true, className = "", style = {}, ...props }, ref) => { const [hoveredNode, setHoveredNode] = useState(null); const [selectedNode, setSelectedNode] = useState(null); const [currentZoom, setCurrentZoom] = useState(zoomLevel); const [growthProgress, setGrowthProgress] = useState(0); const containerRef = useRef(null); const animationRef = useRef(); const { prefersReducedMotion } = useMotionPreference(); const layoutId = useA11yId(); const { play } = useGlassSound(); useEffect(() => { if (animateGrowth && !prefersReducedMotion) { const startTime = Date.now(); const duration = 2000; // 2 seconds const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); setGrowthProgress(progress); if (progress < 1) { animationRef.current = requestAnimationFrame(animate); } }; animationRef.current = requestAnimationFrame(animate); } else { setGrowthProgress(1); } return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [animateGrowth, prefersReducedMotion]); const generateFractalPositions = useCallback((nodes, depth = 0, parentPos = { x: 0, y: 0 }, parentAngle = -90, parentScale = initialScale) => { if (depth >= maxDepth || depth >= growthProgress * maxDepth) return []; return nodes.map((node, index) => { const currentScale = parentScale * Math.pow(scaleFactor, depth); let position = { x: 0, y: 0 }; let rotation = 0; switch (fractalType) { case "tree": const angleOffset = (index - (nodes.length - 1) / 2) * branchAngle; const currentAngle = parentAngle + angleOffset; const distance = 100 * currentScale; position = { x: parentPos.x + Math.cos(currentAngle * Math.PI / 180) * distance, y: parentPos.y + Math.sin(currentAngle * Math.PI / 180) * distance }; rotation = currentAngle + 90; break; case "spiral": const spiralAngle = parentAngle + index * 137.5; // Golden angle const spiralRadius = depth * 50 * currentScale; position = { x: parentPos.x + Math.cos(spiralAngle * Math.PI / 180) * spiralRadius, y: parentPos.y + Math.sin(spiralAngle * Math.PI / 180) * spiralRadius }; rotation = spiralAngle; break; case "sierpinski": // Sierpinski triangle pattern const triAngle = index * 120 + parentAngle; const triRadius = 80 * currentScale; position = { x: parentPos.x + Math.cos(triAngle * Math.PI / 180) * triRadius, y: parentPos.y + Math.sin(triAngle * Math.PI / 180) * triRadius }; break; case "mandelbrot": const z = { x: index * 0.1, y: depth * 0.1 }; position = { x: parentPos.x + z.x * 100 * currentScale, y: parentPos.y + z.y * 100 * currentScale }; break; default: position = { x: parentPos.x + (index - nodes.length / 2) * 80 * currentScale, y: parentPos.y + depth * 60 * currentScale }; } const processedNode = { ...node, depth, scale: currentScale, rotation, position, children: node.children && recursive ? generateFractalPositions(node.children, depth + 1, position, rotation || parentAngle, currentScale) : [] }; return processedNode; }); }, [maxDepth, fractalType, scaleFactor, branchAngle, initialScale, recursive, growthProgress]); const processedNodes = useMemo(() => { const rootPosition = centerNode ? { x: 0, y: 0 } : { x: -200, y: -200 }; return generateFractalPositions(nodes, 0, rootPosition); }, [nodes, generateFractalPositions, centerNode]); const flattenNodes = nodes => { return nodes.reduce((acc, node) => { acc.push(node); if (node.children) { acc.push(...flattenNodes(node.children)); } return acc; }, []); }; const allNodes = useMemo(() => flattenNodes(processedNodes), [processedNodes]); const handleNodeClick = useCallback(node => { setSelectedNode(node.id); onNodeClick?.(node); if (soundEnabled) { play("click"); } }, [onNodeClick, soundEnabled, play]); const handleNodeHover = useCallback(node => { setHoveredNode(node?.id || null); onNodeHover?.(node); if (soundEnabled && node) { play("hover"); } }, [onNodeHover, soundEnabled, play]); const handleWheel = useCallback(e => { if (!interactiveZoom) return; e.preventDefault(); const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1; setCurrentZoom(prev => Math.max(0.1, Math.min(5, prev * zoomDelta))); }, [interactiveZoom]); const getNodeVariants = () => ({ hidden: { scale: 0, opacity: 0 }, visible: delay => ({ scale: 1, opacity: 1, transition: { type: "spring", tension: 300, friction: 25, delay: prefersReducedMotion ? 0 : delay * 0.1 } }), hover: { scale: 1.1, transition: { type: "spring", tension: 400, friction: 20 } }, selected: { scale: 1.2, transition: { type: "spring", tension: 400, friction: 20 } } }); return jsxs(OptimizedGlassCore, { ref: ref, className: `glass-fractal-layout relative overflow-hidden ${className}`, style: { width: "100%", height: "600px", ...style }, glassConfig: { blur: 15, opacity: 0.9, saturation: 1.1, brightness: 1.05, ...glassConfig }, onWheel: handleWheel, role: "application", "aria-label": "Fractal layout visualization", id: layoutId, ...props, children: [jsx("div", { ref: containerRef, className: 'absolute inset-0 glass-flex glass-items-center glass-justify-center', style: { transform: `scale(${currentZoom})`, transformOrigin: "center" }, children: jsx(AnimatePresence, { children: allNodes.map((node, index) => { const isHovered = hoveredNode === node.id; const isSelected = selectedNode === node.id; const nodeScale = (node.scale || 1) * currentZoom; return jsxs(motion.div, { className: 'absolute cursor-pointer', style: { left: "50%", top: "50%", transform: `translate(-50%, -50%) translate(${node.position?.x || 0}px, ${node.position?.y || 0}px) scale(${nodeScale}) rotate(${node.rotation || 0}deg)` }, custom: node.depth || 0, variants: getNodeVariants(), initial: "hidden", animate: isSelected ? "selected" : isHovered ? "hover" : "visible", exit: "hidden", onMouseEnter: () => handleNodeHover(node), onMouseLeave: () => handleNodeHover(null), onClick: () => handleNodeClick(node), children: [jsx("div", { className: ` glass-surface rounded-lg border border-white/20 glass-backdrop-blur-md transition-all duration-200 p-2 min-w-[40px] min-h-[40px] flex items-center justify-center ${isHovered || isSelected ? "bg-white/20 border-white/40" : "bg-white/10 border-white/20"} `, style: { opacity: Math.max(0.3, 1 - (node.depth || 0) * 0.2) }, children: node.content }), node.children?.map((child, childIndex) => jsx("div", { className: 'absolute glass-border-l glass-border-white/30', style: { left: "50%", top: "50%", height: Math.sqrt(Math.pow((child.position?.x || 0) - (node.position?.x || 0), 2) + Math.pow((child.position?.y || 0) - (node.position?.y || 0), 2)), transformOrigin: "0 0", transform: `rotate(${Math.atan2((child.position?.y || 0) - (node.position?.y || 0), (child.position?.x || 0) - (node.position?.x || 0))}rad)` } }, `line-${child.id}`)), (node.depth || 0) > 0 && jsx("div", { className: 'absolute glass-top-1 -right-1 glass-surface-dark/50 text-primary glass-text-xs glass-radius-full w-4 h-4 glass-flex glass-items-center glass-justify-center', children: node.depth })] }, `${node.id}-${node.depth}`); }) }) }), jsxs("div", { className: 'absolute bottom-4 left-4 glass-flex glass-flex-col glass-gap-2', children: [jsxs("div", { className: 'glass-text-xs text-primary/70 glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard', children: ["Type: ", fractalType] }), jsxs("div", { className: 'glass-text-xs text-primary/70 glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard', children: ["Depth: ", Math.floor(growthProgress * maxDepth), "/", maxDepth] }), jsxs("div", { className: 'glass-text-xs text-primary/70 glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard', children: ["Nodes: ", allNodes.length] }), interactiveZoom && jsxs("div", { className: 'glass-text-xs text-primary/70 glass-surface-dark/20 glass-px-2 glass-py-1 glass-radius glass-glass-backdrop-blur-sm glass-contrast-guard', children: ["Zoom: ", (currentZoom * 100).toFixed(0), "%"] })] }), animateGrowth && growthProgress < 1 && jsx("div", { className: 'absolute top-4 right-4', children: jsx("div", { className: 'w-32 h-2 glass-surface-dark/20 glass-radius-full glass-glass-backdrop-blur-sm glass-contrast-guard', children: jsx("div", { className: 'glass-h-full glass-surface-subtle/50 glass-radius-full transition-all duration-100', style: { width: `${growthProgress * 100}%` } }) }) })] }); }); GlassFractalLayout.displayName = "GlassFractalLayout"; export { GlassFractalLayout }; //# sourceMappingURL=GlassFractalLayout.js.map