aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
446 lines (443 loc) • 16 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { useReducedMotion } from '../../hooks/useReducedMotion.js';
import { useRef, useState, useCallback, useEffect, useMemo } from 'react';
import { useAnimation, useSpring, motion } from 'framer-motion';
const IS_TEST_ENV = typeof process !== "undefined" && process.env?.JEST_WORKER_ID !== undefined;
// Default spatial position
const DEFAULT_POSITION = {
x: 0,
y: 0,
z: 0,
pitch: 0,
yaw: 0,
roll: 0
};
// Spatial context detection
const detectSpatialContext = () => {
const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
// Check for WebXR support
const hasWebXR = typeof navigator !== "undefined" && "xr" in navigator;
const isVR = hasWebXR && userAgent.includes("VR");
const isAR = hasWebXR && (userAgent.includes("AR") || userAgent.includes("Mobile"));
// Basic capability detection
const capabilities = {
headTracking: hasWebXR,
handTracking: hasWebXR && "getInputSources" in window,
eyeTracking: false,
// Requires specific hardware/API
bodyTracking: false,
// Limited availability
environmentMapping: hasWebXR,
occlusion: hasWebXR,
lighting: hasWebXR,
physics: true // Software-based physics always available
};
let environment = "desktop";
if (isVR) environment = "vr-headset";else if (isAR && userAgent.includes("Mobile")) environment = "ar-phone";else if (isAR) environment = "ar-glasses";else if (userAgent.includes("Mobile")) environment = "mobile";
return {
environment,
capabilities,
displayInfo: {
fov: environment.includes("vr") ? 110 : 60,
resolution: {
width: window.innerWidth || 1920,
height: window.innerHeight || 1080
},
refreshRate: 60 // Default assumption
}
};
};
const SpatialComputingEngine = ({
children,
className = "",
position = {},
bounds,
enableGestures = true,
enableAnchoring = false,
enablePhysics = true,
enableOcclusion = false,
gestureTypes = ["tap", "pinch", "grab"],
spatialId,
onGesture,
onPositionChange,
onAnchorUpdate,
showDebugHud = false
}) => {
useReducedMotion();
const containerRef = useRef(null);
const xrSessionRef = useRef(null); // XRSession type not available in current environment
const [spatialContext, setSpatialContext] = useState(detectSpatialContext());
const [currentPosition, setCurrentPosition] = useState({
...DEFAULT_POSITION,
...position
});
const [spatialAnchor, setSpatialAnchor] = useState(null);
const [gestureActive, setGestureActive] = useState(null);
const controls = useAnimation();
// Spatial motion values for smooth transformations
const spatialX = useSpring(currentPosition.x, {
stiffness: 400,
damping: 40
});
const spatialY = useSpring(currentPosition.y, {
stiffness: 400,
damping: 40
});
const spatialZ = useSpring(currentPosition.z, {
stiffness: 400,
damping: 40
});
const spatialPitch = useSpring(currentPosition.pitch, {
stiffness: 300,
damping: 35
});
const spatialYaw = useSpring(currentPosition.yaw, {
stiffness: 300,
damping: 35
});
const spatialRoll = useSpring(currentPosition.roll, {
stiffness: 300,
damping: 35
});
// WebXR session management
const initializeXRSession = useCallback(async () => {
if (IS_TEST_ENV || !spatialContext.capabilities.headTracking) return;
try {
if ("xr" in navigator) {
const xr = navigator.xr; // XRSystem type not available in current environment
// Check for immersive VR support
const isVRSupported = await xr.isSessionSupported("immersive-vr");
const isARSupported = await xr.isSessionSupported("immersive-ar");
if (isVRSupported || isARSupported) {
const sessionMode = isVRSupported ? "immersive-vr" : "immersive-ar";
const session = await xr.requestSession(sessionMode, {
requiredFeatures: ["local"],
optionalFeatures: ["hand-tracking", "eye-tracking", "anchors"]
});
xrSessionRef.current = session;
// Set up XR frame loop
const onXRFrame = (time, frame) => {
// XRFrame type not available in current environment
const pose = frame.getViewerPose(session.renderState.baseLayer.framebuffer);
if (pose) {
updateSpatialPosition(pose);
}
session.requestAnimationFrame(onXRFrame);
};
session.requestAnimationFrame(onXRFrame);
}
}
} catch (error) {
console.warn("XR session initialization failed:", error);
}
}, [spatialContext]);
// Update spatial position from XR pose or other input
const updateSpatialPosition = useCallback(pose => {
// XRPose type not available in current environment
let newPosition;
if (pose && "transform" in pose) {
// Extract position from XR pose
const {
position: pos,
orientation
} = pose.transform;
newPosition = {
x: pos.x,
y: pos.y,
z: pos.z,
pitch: Math.atan2(2 * (orientation.w * orientation.x + orientation.y * orientation.z), 1 - 2 * (orientation.x * orientation.x + orientation.y * orientation.y)) * 180 / Math.PI,
yaw: Math.asin(2 * (orientation.w * orientation.y - orientation.z * orientation.x)) * 180 / Math.PI,
roll: Math.atan2(2 * (orientation.w * orientation.z + orientation.x * orientation.y), 1 - 2 * (orientation.y * orientation.y + orientation.z * orientation.z)) * 180 / Math.PI
};
} else {
// Use provided position data
newPosition = {
...currentPosition,
...pose
};
}
// Apply bounds constraints
if (bounds) {
newPosition.x = Math.max(bounds.min.x, Math.min(bounds.max.x, newPosition.x));
newPosition.y = Math.max(bounds.min.y, Math.min(bounds.max.y, newPosition.y));
newPosition.z = Math.max(bounds.min.z, Math.min(bounds.max.z, newPosition.z));
if (bounds.constraints?.lockX) newPosition.x = currentPosition.x;
if (bounds.constraints?.lockY) newPosition.y = currentPosition.y;
if (bounds.constraints?.lockZ) newPosition.z = currentPosition.z;
if (bounds.constraints?.lockRotation) {
newPosition.pitch = currentPosition.pitch;
newPosition.yaw = currentPosition.yaw;
newPosition.roll = currentPosition.roll;
}
}
setCurrentPosition(newPosition);
// Update motion values
spatialX.set(newPosition.x);
spatialY.set(newPosition.y);
spatialZ.set(newPosition.z);
spatialPitch.set(newPosition.pitch);
spatialYaw.set(newPosition.yaw);
spatialRoll.set(newPosition.roll);
onPositionChange?.(newPosition);
}, [currentPosition, bounds, spatialX, spatialY, spatialZ, spatialPitch, spatialYaw, spatialRoll, onPositionChange]);
// Gesture recognition system
const recognizeGesture = useCallback(event => {
if (!enableGestures) return null;
const rect = containerRef.current?.getBoundingClientRect();
if (!rect) return null;
let gestureType = null;
let position = {
...DEFAULT_POSITION
};
let confidence = 1;
if (event instanceof PointerEvent) {
// Basic pointer gesture recognition
const x = (event.clientX - rect.left) / rect.width - 0.5;
const y = (event.clientY - rect.top) / rect.height - 0.5;
position = {
...DEFAULT_POSITION,
x: x * 100,
y: y * 100
};
if (event.type === "pointerdown") gestureType = "tap";else if (event.pressure > 0.5) gestureType = "grab";
} else if (event instanceof TouchEvent && event.touches.length > 0) {
// Multi-touch gesture recognition
const touch = event.touches[0];
const x = (touch.clientX - rect.left) / rect.width - 0.5;
const y = (touch.clientY - rect.top) / rect.height - 0.5;
position = {
...DEFAULT_POSITION,
x: x * 100,
y: y * 100
};
if (event.touches.length === 1) gestureType = "tap";else if (event.touches.length === 2) gestureType = "pinch";
confidence = Math.min(1, event.touches.length / 2);
}
if (gestureType && gestureTypes.includes(gestureType)) {
const gestureEvent = {
type: gestureType,
position,
velocity: {
...DEFAULT_POSITION
},
// Would calculate from previous positions
confidence,
duration: 0 // Would track duration
};
return gestureEvent;
}
return null;
}, [enableGestures, gestureTypes]);
// Handle gesture events
const handleGestureStart = useCallback(event => {
const gesture = recognizeGesture(event);
if (gesture) {
setGestureActive(gesture.type);
onGesture?.(gesture);
// Visual feedback for gesture
controls.start({
scale: 1.05,
transition: {
duration: 0.1
}
});
}
}, [recognizeGesture, onGesture, controls]);
const handleGestureEnd = useCallback(() => {
if (gestureActive) {
setGestureActive(null);
controls.start({
scale: 1,
transition: {
duration: 0.2
}
});
}
}, [gestureActive, controls]);
// Spatial anchoring system
const createSpatialAnchor = useCallback(async () => {
if (!enableAnchoring || !spatialId) return;
const anchor = {
id: spatialId,
position: currentPosition,
persistent: true,
cloudSync: false,
accuracy: 1.0,
lastUpdate: Date.now()
};
// In a real implementation, this would persist to spatial mapping system
setSpatialAnchor(anchor);
onAnchorUpdate?.(anchor);
}, [enableAnchoring, spatialId, currentPosition, onAnchorUpdate]);
// Physics simulation for spatial objects
const applyPhysics = useCallback(deltaTime => {
if (!enablePhysics) return;
// Simple gravity and collision detection
const gravity = -9.81; // m/s²
const newY = currentPosition.y + gravity * deltaTime * deltaTime / 1000;
// Ground collision
if (newY < 0) {
updateSpatialPosition({
y: 0
});
} else {
updateSpatialPosition({
y: newY
});
}
}, [enablePhysics, currentPosition.y, updateSpatialPosition]);
// Initialize XR session on mount
useEffect(() => {
initializeXRSession();
// Cleanup XR session on unmount
return () => {
if (xrSessionRef.current) {
xrSessionRef.current.end();
}
};
}, [initializeXRSession]);
// Set up gesture event listeners
useEffect(() => {
const container = containerRef.current;
if (!container || !enableGestures) return;
const handlePointerDown = e => handleGestureStart(e);
const handleTouchStart = e => handleGestureStart(e);
const handlePointerUp = () => handleGestureEnd();
const handleTouchEnd = () => handleGestureEnd();
container.addEventListener("pointerdown", handlePointerDown);
container.addEventListener("touchstart", handleTouchStart);
container.addEventListener("pointerup", handlePointerUp);
container.addEventListener("touchend", handleTouchEnd);
return () => {
container.removeEventListener("pointerdown", handlePointerDown);
container.removeEventListener("touchstart", handleTouchStart);
container.removeEventListener("pointerup", handlePointerUp);
container.removeEventListener("touchend", handleTouchEnd);
};
}, [enableGestures, handleGestureStart, handleGestureEnd]);
// Physics update loop
useEffect(() => {
if (!enablePhysics) return;
if (IS_TEST_ENV) {
applyPhysics(16);
return;
}
let lastTime = Date.now();
let animationFrame;
const physicsLoop = () => {
const currentTime = Date.now();
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
applyPhysics(deltaTime);
animationFrame = requestAnimationFrame(physicsLoop);
};
animationFrame = requestAnimationFrame(physicsLoop);
return () => {
if (animationFrame !== undefined) {
cancelAnimationFrame(animationFrame);
}
};
}, [enablePhysics, applyPhysics]);
// Auto-create anchor if position is stable
useEffect(() => {
if (!enableAnchoring || spatialAnchor) return;
if (IS_TEST_ENV) {
createSpatialAnchor();
return;
}
const timer = setTimeout(createSpatialAnchor, 5000);
return () => clearTimeout(timer);
}, [enableAnchoring, spatialAnchor, createSpatialAnchor]);
// Transform calculations for CSS
const transform3D = useMemo(() => ({
x: spatialX,
y: spatialY,
z: spatialZ,
rotateX: spatialPitch,
rotateY: spatialYaw,
rotateZ: spatialRoll
}), [spatialX, spatialY, spatialZ, spatialPitch, spatialYaw, spatialRoll]);
return jsxs(motion.div, {
ref: containerRef,
className: `spatial-computing-container ${className}`,
style: {
position: "relative",
transformStyle: "preserve-3d",
perspective: "1000px",
...transform3D
},
animate: controls,
children: [bounds && process.env.NODE_ENV === "development" && jsx("div", {
className: 'spatial-bounds-debug',
style: {
position: "absolute",
border: "1px dashed rgba(0, 255, 0, 0.5)",
left: `${bounds.min.x}px`,
top: `${bounds.min.y}px`,
width: `${bounds.max.x - bounds.min.x}px`,
height: `${bounds.max.y - bounds.min.y}px`,
pointerEvents: "none"
}
}), spatialAnchor && jsx("div", {
className: 'spatial-anchor-indicator',
style: {
position: "absolute",
top: "10px",
left: "10px",
width: "8px",
height: "8px",
backgroundColor: '/* Use createGlassStyle({ intent: "neutral", elevation: "level2" }) */',
borderRadius: "50%",
pointerEvents: "none"
}
}), gestureActive && jsx("div", {
className: 'gesture-feedback',
style: {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
background: '/* Use createGlassStyle({ intent: "neutral", elevation: "level2" }) */',
borderRadius: "50%",
width: "60px",
height: "60px",
pointerEvents: "none",
animation: "pulse 1s infinite"
}
}), enableOcclusion && spatialContext.capabilities.occlusion && jsx("div", {
className: 'occlusion-layer',
style: {
position: "absolute",
inset: 0,
background: '/* Use createGlassStyle({ intent: "primary", elevation: "level2" }) */',
pointerEvents: "none",
mixBlendMode: "multiply"
}
}), children, (showDebugHud || process.env.NODE_ENV === "development") && jsxs("div", {
className: 'spatial-debug-info',
style: {
position: "absolute",
bottom: "10px",
right: "10px",
background: '/* Use createGlassStyle({ intent: "primary", elevation: "level2" }) */',
color: "white",
padding: "8px",
fontSize: "12px",
borderRadius: "4px",
pointerEvents: "none",
fontFamily: "monospace"
},
children: ["Env: ", spatialContext.environment, jsx("br", {}), "Pos: (", currentPosition.x.toFixed(1), ", ", currentPosition.y.toFixed(1), ",", " ", currentPosition.z.toFixed(1), ")", jsx("br", {}), "Rot: (", currentPosition.pitch.toFixed(1), "\u00B0,", " ", currentPosition.yaw.toFixed(1), "\u00B0, ", currentPosition.roll.toFixed(1), "\u00B0)", jsx("br", {}), gestureActive && `Gesture: ${gestureActive}`, jsx("br", {}), spatialAnchor && "Anchored"]
}), jsx("style", {
children: `
@keyframes pulse {
0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
50% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }
}
`
})]
});
};
export { SpatialComputingEngine, SpatialComputingEngine as default };
//# sourceMappingURL=SpatialComputingEngine.js.map