110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
/**
|
|
* DescriptionElement Component
|
|
*
|
|
* Description element with icon or styled text content.
|
|
* Renders with unified wrapper styling + content.
|
|
*/
|
|
|
|
import React, { useMemo } from 'react';
|
|
import type { CSSProperties } from 'react';
|
|
import type { CanvasElement } from '../../../types/constructor';
|
|
import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl';
|
|
import { getFontByKey, getFontStyle } from '../../../lib/fonts';
|
|
import { normalizePixelValue } from '../../../lib/elementStyles';
|
|
|
|
interface DescriptionElementProps {
|
|
element: CanvasElement;
|
|
resolveUrl?: (url: string | undefined) => string;
|
|
className: string;
|
|
style: CSSProperties;
|
|
}
|
|
|
|
const DescriptionElement: React.FC<DescriptionElementProps> = ({
|
|
element,
|
|
resolveUrl,
|
|
className,
|
|
style,
|
|
}) => {
|
|
const resolve = resolveUrl ?? resolveAssetPlaybackUrl;
|
|
|
|
// Resolve font keys to full CSS styles (including fontStretch for condensed variants)
|
|
const titleFontStyle = useMemo(() => {
|
|
const fontKey = element.descriptionTitleFontFamily;
|
|
if (!fontKey) return {};
|
|
const font = getFontByKey(fontKey);
|
|
return font ? getFontStyle(font) : { fontFamily: fontKey };
|
|
}, [element.descriptionTitleFontFamily]);
|
|
|
|
const textFontStyle = useMemo(() => {
|
|
const fontKey = element.descriptionTextFontFamily;
|
|
if (!fontKey) return {};
|
|
const font = getFontByKey(fontKey);
|
|
return font ? getFontStyle(font) : { fontFamily: fontKey };
|
|
}, [element.descriptionTextFontFamily]);
|
|
|
|
// With icon: render image
|
|
if (element.iconUrl) {
|
|
const imgStyle: CSSProperties = {
|
|
width: element.width ? '100%' : 'auto',
|
|
height: element.height ? '100%' : 'auto',
|
|
objectFit: 'contain',
|
|
// Override Tailwind preflight's max-width: 100% which causes shrinking near canvas edges
|
|
maxWidth: 'none',
|
|
};
|
|
|
|
return (
|
|
<div className={className} style={style}>
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img
|
|
src={resolve(element.iconUrl)}
|
|
alt='Description'
|
|
style={imgStyle}
|
|
draggable={false}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Without icon: render styled text description
|
|
// Background color is controlled via CSS Styles tab (backgroundColor property)
|
|
// Inheritable styles (color, fontSize, fontWeight) cascade from General Element Styles
|
|
// when section-specific values are not set
|
|
|
|
// Build title style - only set properties if explicitly configured
|
|
// fontWeight cascades from General Element Styles via CSS inheritance
|
|
const titleStyle: CSSProperties = {
|
|
...titleFontStyle,
|
|
};
|
|
if (element.descriptionTitleFontSize) {
|
|
titleStyle.fontSize = normalizePixelValue(element.descriptionTitleFontSize);
|
|
}
|
|
if (element.descriptionTitleColor) {
|
|
titleStyle.color = element.descriptionTitleColor;
|
|
}
|
|
|
|
// Build text style - only set properties if explicitly configured
|
|
// fontWeight cascades from General Element Styles via CSS inheritance
|
|
const textStyle: CSSProperties = {
|
|
...textFontStyle,
|
|
};
|
|
if (element.descriptionTextFontSize) {
|
|
textStyle.fontSize = normalizePixelValue(element.descriptionTextFontSize);
|
|
}
|
|
if (element.descriptionTextColor) {
|
|
textStyle.color = element.descriptionTextColor;
|
|
}
|
|
|
|
return (
|
|
<div className={className} style={style}>
|
|
<div className='p-4'>
|
|
<p style={titleStyle}>{element.descriptionTitle || ''}</p>
|
|
{element.descriptionText && (
|
|
<p style={textStyle}>{element.descriptionText}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DescriptionElement;
|