39948-vm/frontend/src/components/UiElements/UiElementRenderer.tsx

138 lines
4.4 KiB
TypeScript

/**
* UiElementRenderer Component
*
* Unified UI Element Renderer - single source of truth for element rendering.
* Used by both CanvasElement (constructor) and RuntimeElement (presentation)
* to ensure WYSIWYG consistency.
*
* Renders any UI element with consistent styling by delegating to per-type components.
*/
import React from 'react';
import type { CanvasElement } from '../../types/constructor';
import type { ResolvedTransitionSettings } from '../../types/transition';
import type { PreloadCacheProvider } from '../../hooks/video';
import { useElementWrapperStyle } from './shared/useElementWrapperStyle';
import {
isNavigationElementType,
isTooltipElementType,
isDescriptionElementType,
isGalleryElementType,
isCarouselElementType,
isVideoPlayerElementType,
isAudioPlayerElementType,
isLogoElementType,
isSpotElementType,
isPopupElementType,
} from '../../lib/elementDefaults';
// Import per-type components
import NavigationElement from './elements/NavigationElement';
import GalleryElement from './elements/GalleryElement';
import TooltipElement from './elements/TooltipElement';
import DescriptionElement from './elements/DescriptionElement';
import CarouselElement from './elements/CarouselElement';
import LogoElement from './elements/LogoElement';
import SpotElement from './elements/SpotElement';
import VideoPlayerElement from './elements/VideoPlayerElement';
import AudioPlayerElement from './elements/AudioPlayerElement';
import PopupElement from './elements/PopupElement';
export interface UiElementRendererProps {
element: CanvasElement;
resolveUrl?: (url: string | undefined) => string;
// Constructor-specific props (optional)
isSelected?: boolean;
isEditMode?: boolean;
// Gallery carousel callback
onGalleryCardClick?: (cardIndex: number) => void;
// Carousel-specific callback for button position changes (constructor only)
onCarouselButtonPositionChange?: (
button: 'prev' | 'next',
x: number,
y: number,
) => void;
// Letterbox styles for constraining fullscreen elements to canvas bounds
letterboxStyles?: React.CSSProperties;
// Page transition settings (for slide transition cascade in carousel/gallery)
pageTransitionSettings?: ResolvedTransitionSettings;
// Preload cache provider for video elements
preloadCache?: PreloadCacheProvider;
}
/**
* Unified UI Element Renderer
*
* Renders any UI element with consistent styling.
* Used by both CanvasElement (constructor) and RuntimeElement (presentation).
*/
export const UiElementRenderer: React.FC<UiElementRendererProps> = ({
element,
resolveUrl,
isSelected = false,
isEditMode = false,
onGalleryCardClick,
onCarouselButtonPositionChange,
letterboxStyles,
pageTransitionSettings,
preloadCache,
}) => {
const { className, style } = useElementWrapperStyle({
element,
isSelected,
isEditMode,
});
// Common props for all element types
const commonProps = { element, resolveUrl, className, style };
// Delegate to type-specific component
if (isNavigationElementType(element.type)) {
return <NavigationElement {...commonProps} />;
}
if (isGalleryElementType(element.type)) {
return <GalleryElement {...commonProps} onCardClick={onGalleryCardClick} />;
}
if (isTooltipElementType(element.type)) {
return <TooltipElement {...commonProps} />;
}
if (isDescriptionElementType(element.type)) {
return <DescriptionElement {...commonProps} />;
}
if (isCarouselElementType(element.type)) {
return (
<CarouselElement
{...commonProps}
isEditMode={isEditMode}
onButtonPositionChange={onCarouselButtonPositionChange}
letterboxStyles={letterboxStyles}
pageTransitionSettings={pageTransitionSettings}
/>
);
}
if (isVideoPlayerElementType(element.type)) {
return <VideoPlayerElement {...commonProps} preloadCache={preloadCache} />;
}
if (isAudioPlayerElementType(element.type)) {
return <AudioPlayerElement {...commonProps} />;
}
if (isLogoElementType(element.type)) {
return <LogoElement {...commonProps} />;
}
if (isSpotElementType(element.type)) {
return <SpotElement {...commonProps} />;
}
if (isPopupElementType(element.type)) {
return <PopupElement {...commonProps} />;
}
// Fallback for unknown types
return (
<div className={className} style={style}>
<span className='px-4 py-2 text-sm'>{element.label || element.type}</span>
</div>
);
};
export default UiElementRenderer;