104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
/**
|
|
* RuntimeElement Component
|
|
*
|
|
* Renders a single UI element with interactive effects at runtime.
|
|
* Handles hover, focus, and active states for element effects.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { useElementEffects } from '../hooks/useElementEffects';
|
|
import { buildElementStyle } from '../lib/elementStyles';
|
|
import {
|
|
buildTransitionStyle,
|
|
buildAppearAnimationStyle,
|
|
hasAnyEffects,
|
|
type ElementEffectProperties,
|
|
} from '../lib/elementEffects';
|
|
|
|
interface RuntimeElementProps {
|
|
element: any;
|
|
onClick: () => void;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
const RuntimeElement: React.FC<RuntimeElementProps> = ({
|
|
element,
|
|
onClick,
|
|
children,
|
|
}) => {
|
|
const xPercent = element.xPercent ?? 0;
|
|
const yPercent = element.yPercent ?? 0;
|
|
const rotation = element.rotation ?? 0;
|
|
|
|
// Extract effect properties from element
|
|
const effectProperties: Partial<ElementEffectProperties> = {
|
|
appearAnimation: element.appearAnimation,
|
|
appearAnimationDuration: element.appearAnimationDuration,
|
|
appearAnimationEasing: element.appearAnimationEasing,
|
|
hoverScale: element.hoverScale,
|
|
hoverOpacity: element.hoverOpacity,
|
|
hoverBackgroundColor: element.hoverBackgroundColor,
|
|
hoverColor: element.hoverColor,
|
|
hoverBoxShadow: element.hoverBoxShadow,
|
|
hoverTransitionDuration: element.hoverTransitionDuration,
|
|
focusScale: element.focusScale,
|
|
focusOpacity: element.focusOpacity,
|
|
focusOutline: element.focusOutline,
|
|
focusBoxShadow: element.focusBoxShadow,
|
|
activeScale: element.activeScale,
|
|
activeOpacity: element.activeOpacity,
|
|
activeBackgroundColor: element.activeBackgroundColor,
|
|
};
|
|
|
|
// Use effects hook for interactive states
|
|
const { effectStyle, eventHandlers } = useElementEffects(effectProperties);
|
|
|
|
// Build base element style
|
|
const baseStyle: React.CSSProperties = {
|
|
left: `${xPercent}%`,
|
|
top: `${yPercent}%`,
|
|
transform: `translate(-50%, -50%)${rotation ? ` rotate(${rotation}deg)` : ''}`,
|
|
...buildElementStyle(element),
|
|
};
|
|
|
|
// Merge transform if effect style has transform
|
|
let mergedStyle: React.CSSProperties = { ...baseStyle };
|
|
|
|
// Handle transform merging - effect transform overrides base (except for position)
|
|
if (effectStyle.transform) {
|
|
// Preserve the translate and rotation, add effect transform
|
|
mergedStyle.transform = `translate(-50%, -50%)${rotation ? ` rotate(${rotation}deg)` : ''} ${effectStyle.transform}`;
|
|
// Remove transform from effectStyle to avoid double application
|
|
const { transform, ...restEffectStyle } = effectStyle;
|
|
mergedStyle = { ...mergedStyle, ...restEffectStyle };
|
|
} else {
|
|
mergedStyle = { ...mergedStyle, ...effectStyle };
|
|
}
|
|
|
|
// Add transition if element has any effects
|
|
if (hasAnyEffects(effectProperties)) {
|
|
const transitionStyle = buildTransitionStyle(effectProperties);
|
|
mergedStyle = { ...mergedStyle, ...transitionStyle };
|
|
}
|
|
|
|
// Add appear animation if configured
|
|
if (effectProperties.appearAnimation) {
|
|
const animationStyle = buildAppearAnimationStyle(effectProperties);
|
|
mergedStyle = { ...mergedStyle, ...animationStyle };
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className='absolute cursor-pointer'
|
|
style={mergedStyle}
|
|
onClick={onClick}
|
|
tabIndex={0}
|
|
{...eventHandlers}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RuntimeElement;
|