UNPKG

aura-glass

Version:

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

297 lines (294 loc) 10.8 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import React, { useRef, useState, useCallback, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { cn } from '../../lib/utilsComprehensive.js'; const createDefaultSegment = () => { if (typeof window === "undefined") { return { left: 0, top: 0, width: 1024, height: 768, devicePixelRatio: 1 }; } return { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio || 1 }; }; function GlassFoldableSupport({ children, className, adaptiveLayout = true, bridgeHinge = true, independentSegments = false, continuousGlass = true, foldAnimation = true, onFoldStateChange }) { const isBrowser = typeof window !== "undefined"; const prefersReducedMotion = useReducedMotion(); const containerRef = useRef(null); const [foldableInfo, setFoldableInfo] = useState({ isFoldable: false, foldState: "unknown", segments: [createDefaultSegment()] }); const [layoutMode, setLayoutMode] = useState("single"); // Detect foldable device capabilities const detectFoldableCapabilities = useCallback(async () => { if (!isBrowser) return; // Check for Visual Viewport API and Screen Segments if ("getScreenDetails" in window) { try { const screenDetails = await window.getScreenDetails(); const segments = screenDetails.screens.map(screen => ({ left: screen.left, top: screen.top, width: screen.width, height: screen.height, devicePixelRatio: screen.devicePixelRatio })); const isFoldable = segments.length > 1; let hinge = undefined; if (isFoldable && segments.length === 2) { // Calculate hinge position const [segment1, segment2] = segments; if (segment1.top === segment2.top && segment1.height === segment2.height) { // Vertical hinge (side by side) hinge = { position: "vertical", offset: Math.max(segment1.left + segment1.width, segment2.left + segment2.width) - Math.min(segment1.left, segment2.left), width: Math.abs(segment2.left - (segment1.left + segment1.width)) }; } else if (segment1.left === segment2.left && segment1.width === segment2.width) { // Horizontal hinge (top and bottom) hinge = { position: "horizontal", offset: Math.max(segment1.top + segment1.height, segment2.top + segment2.height) - Math.min(segment1.top, segment2.top), width: Math.abs(segment2.top - (segment1.top + segment1.height)) }; } } setFoldableInfo({ isFoldable, foldState: isFoldable ? "unfolded" : "unknown", segments, hinge }); setLayoutMode(isFoldable ? segments.length > 2 ? "extended" : "dual" : "single"); } catch (error) { console.warn("Screen Details API not supported:", error); } } // Fallback: Check for CSS environment variables const supportsSpanning = typeof CSS !== "undefined" && typeof CSS.supports === "function" && (CSS.supports("(spanning: single-fold-vertical)") || CSS.supports("(spanning: single-fold-horizontal)")); if (supportsSpanning) { const spanning = getComputedStyle(document.documentElement).getPropertyValue("env(fold-left)") || getComputedStyle(document.documentElement).getPropertyValue("env(fold-top)"); if (spanning) { setFoldableInfo(prev => ({ ...prev, isFoldable: true, foldState: "unfolded" })); setLayoutMode("dual"); } } // Check for dual-screen using media queries const matchesMedia = query => { if (!isBrowser || typeof window.matchMedia !== "function") { return false; } return window.matchMedia(query).matches; }; const isDualScreen = matchesMedia("(spanning: single-fold-vertical)") || matchesMedia("(spanning: single-fold-horizontal)"); if (isDualScreen) { setFoldableInfo(prev => ({ ...prev, isFoldable: true, foldState: "unfolded" })); setLayoutMode("dual"); } }, []); // Monitor fold state changes const monitorFoldState = useCallback(() => { if (!isBrowser) { return () => undefined; } // Listen for orientation changes that might indicate folding const handleOrientationChange = () => { setTimeout(detectFoldableCapabilities, 100); }; // Listen for resize events that might indicate folding const handleResize = () => { detectFoldableCapabilities(); }; // Listen for visibility changes (device folding might hide content) const handleVisibilityChange = () => { if (document.hidden) { setFoldableInfo(prev => ({ ...prev, foldState: "folded" })); } else { setFoldableInfo(prev => ({ ...prev, foldState: prev.isFoldable ? "unfolded" : "unknown" })); } }; window.addEventListener("orientationchange", handleOrientationChange); window.addEventListener("resize", handleResize); document.addEventListener("visibilitychange", handleVisibilityChange); return () => { window.removeEventListener("orientationchange", handleOrientationChange); window.removeEventListener("resize", handleResize); document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [detectFoldableCapabilities]); // Initialize foldable detection useEffect(() => { if (!isBrowser) return; detectFoldableCapabilities(); const cleanup = monitorFoldState(); return cleanup; }, [detectFoldableCapabilities, monitorFoldState, isBrowser]); // Notify parent of fold state changes useEffect(() => { onFoldStateChange?.(foldableInfo); }, [foldableInfo, onFoldStateChange]); // Generate layout based on foldable info const generateLayout = () => { if (!adaptiveLayout || !foldableInfo.isFoldable) { return jsx("div", { className: 'relative glass-w-full glass-h-full', children: children }); } const { segments, hinge } = foldableInfo; if (independentSegments && segments.length > 1) { // Render independent content for each segment const totalWidth = segments.reduce((acc, segment) => acc + segment.width, 0) || 1; const totalHeight = segments.reduce((acc, segment) => acc + segment.height, 0) || 1; return jsx("div", { className: 'relative glass-w-full glass-h-full glass-flex', children: segments.map((segment, index) => jsx(motion.div, { className: 'relative', style: { width: `${segment.width / totalWidth * 100}%`, height: `${segment.height / totalHeight * 100}%` }, initial: { opacity: 0, scale: 0.95 }, animate: prefersReducedMotion ? {} : { opacity: 1, scale: 1 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.3 }, children: jsx("div", { className: 'OptimizedGlass intensity={0.2} glassBlur={6} glass-w-full glass-h-full', children: React.Children.toArray(children)[index] || children }) }, `segment-${index}`)) }); } // Adaptive single layout with hinge awareness return jsxs("div", { className: 'relative glass-w-full glass-h-full', children: [bridgeHinge && hinge && jsx(HingeBridge, { hinge: hinge, continuousGlass: continuousGlass }), jsx("div", { className: cn("relative", hinge?.position === "vertical" && "flex", hinge?.position === "horizontal" && "flex flex-col"), style: { gap: hinge ? `${hinge.width}px` : undefined }, children: children })] }); }; return jsxs(motion.div, { ref: containerRef, className: cn("relative OptimizedGlass intensity={0.2} glassBlur={6}", "transform-gpu will-change-transform", foldableInfo.isFoldable && "glass-foldable-supported", className), "data-fold-state": foldableInfo.foldState, "data-layout-mode": layoutMode, animate: foldAnimation ? { scale: foldableInfo.foldState === "folded" ? 0.95 : 1, opacity: foldableInfo.foldState === "folded" ? 0.8 : 1 } : undefined, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.3, ease: "easeInOut" }, children: [jsx(AnimatePresence, { mode: "wait", children: jsx(motion.div, { initial: { opacity: 0 }, animate: prefersReducedMotion ? {} : { opacity: 1 }, exit: { opacity: 0 }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.2 }, children: generateLayout() }, `${layoutMode}-${foldableInfo.foldState}`) }), foldableInfo.isFoldable && jsx("div", { className: 'absolute glass-top-2 right-2 glass-surface-primary glass-p-1 glass-radius-sm glass-text-xs opacity-50', children: jsxs("div", { className: "glass-flex glass-items-center glass-gap-1", children: [jsx("div", { className: cn("w-2 h-2 glass-radius-full", foldableInfo.foldState === "folded" && "bg-red-400", foldableInfo.foldState === "unfolded" && "bg-green-400", foldableInfo.foldState === "partial" && "bg-yellow-400", foldableInfo.foldState === "unknown" && "bg-gray-400") }), jsx("span", { children: layoutMode })] }) })] }); } // Hinge bridge component for seamless glass across fold function HingeBridge({ hinge, continuousGlass }) { if (!continuousGlass) return null; return jsx("div", { className: cn("absolute OptimizedGlass intensity={0.2} glassBlur={6} opacity-50", "pointer-events-none", hinge.position === "vertical" && "top-0 bottom-0", hinge.position === "horizontal" && "left-0 right-0"), style: { ...(hinge.position === "vertical" ? { left: `${hinge.offset}px`, width: `${hinge.width}px` } : { top: `${hinge.offset}px`, height: `${hinge.width}px` }), background: "linear-gradient(90deg, transparent, rgba(var(--glass-color-primary) / 0.1), transparent)" } }); } export { GlassFoldableSupport }; //# sourceMappingURL=GlassFoldableSupport.js.map