879 lines
38 KiB
TypeScript
879 lines
38 KiB
TypeScript
/**
|
|
* ElementEditorPanel Component
|
|
*
|
|
* Renders the element editor sidebar in the constructor.
|
|
* Handles element settings, background settings, and transition creation.
|
|
*
|
|
* Uses ConstructorContext for all state - only receives local UI props.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import {
|
|
useConstructorContext,
|
|
useConstructorElements,
|
|
useConstructorBackground,
|
|
useConstructorAssets,
|
|
useConstructorCollectionOps,
|
|
useConstructorDuration,
|
|
useConstructorNavigation,
|
|
useConstructorTransitionCreation,
|
|
useConstructorEditorTab,
|
|
useConstructorMenu,
|
|
} from '../../context/ConstructorContext';
|
|
import {
|
|
ElementSettingsTabsCompact,
|
|
StyleSettingsSectionCompact,
|
|
EffectsSettingsSectionCompact,
|
|
CommonSettingsSectionCompact,
|
|
TooltipSettingsSectionCompact,
|
|
DescriptionSettingsSectionCompact,
|
|
MediaSettingsSectionCompact,
|
|
GallerySettingsSectionCompact,
|
|
CarouselSettingsSectionCompact,
|
|
GalleryCarouselSettingsSectionCompact,
|
|
GallerySectionStyleInputs,
|
|
extractNumericValue,
|
|
} from '../ElementSettings';
|
|
import BackgroundSettingsEditor from './BackgroundSettingsEditor';
|
|
import CreateTransitionForm from './CreateTransitionForm';
|
|
import ElementEditorHeader from './ElementEditorHeader';
|
|
import NavigationSettingsSectionCompact from '../ElementSettings/NavigationSettingsSectionCompact';
|
|
import {
|
|
normalizeAppearDelaySec,
|
|
normalizeAppearDurationSec,
|
|
isNavigationElementType,
|
|
isTooltipElementType,
|
|
isDescriptionElementType,
|
|
isGalleryElementType,
|
|
isCarouselElementType,
|
|
isMediaElementType,
|
|
isVideoPlayerElementType,
|
|
} from '../../lib/elementDefaults';
|
|
import type { CanvasElement } from '../../types/constructor';
|
|
|
|
type NavigationElementType = 'navigation_next' | 'navigation_prev';
|
|
|
|
// ============================================================================
|
|
// Props Interface (Local UI props only)
|
|
// ============================================================================
|
|
|
|
interface ElementEditorPanelProps {
|
|
/** Ref for outside click detection */
|
|
elementEditorRef: React.RefObject<HTMLDivElement | null>;
|
|
/** Draggable position */
|
|
position: { x: number; y: number };
|
|
/** Whether panel is collapsed */
|
|
isCollapsed: boolean;
|
|
/** Toggle collapse state */
|
|
onToggleCollapse: () => void;
|
|
/** Start dragging the panel */
|
|
onDragStart: (event: React.MouseEvent) => void;
|
|
/** Panel title */
|
|
title: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// CSS Property Handler
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Handle CSS property changes with unit conversion
|
|
*/
|
|
const handleCssPropertyChange = (
|
|
prop: string,
|
|
value: string | number | boolean,
|
|
onUpdateElement: (patch: Partial<CanvasElement>) => void,
|
|
) => {
|
|
const numericProps = [
|
|
'width',
|
|
'height',
|
|
'minWidth',
|
|
'maxWidth',
|
|
'minHeight',
|
|
'maxHeight',
|
|
'border',
|
|
'borderRadius',
|
|
'gap',
|
|
];
|
|
|
|
const getUnit = (p: string) => {
|
|
if (['width', 'minWidth', 'maxWidth'].includes(p)) return 'vw';
|
|
if (['height', 'minHeight', 'maxHeight'].includes(p)) return 'vh';
|
|
if (['border', 'borderRadius'].includes(p)) return 'px';
|
|
if (p === 'gap') return 'rem';
|
|
return '';
|
|
};
|
|
|
|
if (numericProps.includes(prop)) {
|
|
const trimmed = String(value || '').trim();
|
|
if (prop === 'border') {
|
|
onUpdateElement({
|
|
[prop]: trimmed ? `${trimmed}px solid currentColor` : 'none',
|
|
});
|
|
} else if (prop === 'borderRadius') {
|
|
onUpdateElement({
|
|
[prop]: trimmed ? `${trimmed}px` : undefined,
|
|
});
|
|
} else {
|
|
const unit = getUnit(prop);
|
|
onUpdateElement({
|
|
[prop]: trimmed ? `${trimmed}${unit}` : undefined,
|
|
});
|
|
}
|
|
} else {
|
|
onUpdateElement({
|
|
[prop]: value || undefined,
|
|
});
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// Component
|
|
// ============================================================================
|
|
|
|
export function ElementEditorPanel({
|
|
elementEditorRef,
|
|
position,
|
|
isCollapsed,
|
|
onToggleCollapse,
|
|
onDragStart,
|
|
title,
|
|
}: ElementEditorPanelProps) {
|
|
// Get state from context
|
|
const {
|
|
selectedElement,
|
|
selectedElementId,
|
|
updateSelectedElement,
|
|
removeSelectedElement,
|
|
} = useConstructorElements();
|
|
|
|
const { selectedMenuItem } = useConstructorMenu();
|
|
|
|
const {
|
|
pageBackground,
|
|
setBackgroundImageUrl,
|
|
setBackgroundVideoUrl,
|
|
setBackgroundAudioUrl,
|
|
setBackgroundVideoSettings,
|
|
} = useConstructorBackground();
|
|
|
|
const { assetOptions } = useConstructorAssets();
|
|
|
|
const { galleryCards, galleryInfoSpans, carouselSlides } =
|
|
useConstructorCollectionOps();
|
|
|
|
const { getDuration, durationNotes } = useConstructorDuration();
|
|
|
|
const {
|
|
pages,
|
|
activePageId,
|
|
allowedNavigationTypes,
|
|
normalizeNavigationType,
|
|
onPreviewTransition,
|
|
} = useConstructorNavigation();
|
|
|
|
const transitionCreation = useConstructorTransitionCreation();
|
|
|
|
const { activeTab, setActiveTab } = useConstructorEditorTab();
|
|
|
|
// ============================================================================
|
|
// Render
|
|
// ============================================================================
|
|
|
|
return (
|
|
<div
|
|
ref={elementEditorRef}
|
|
className={`fixed z-[1000] ${isCollapsed ? 'w-[220px]' : 'w-[300px]'} max-h-[calc(100vh-1rem)] overflow-auto rounded-lg border border-white/30 bg-white/10 backdrop-blur-xl p-2 shadow-xl text-sm`}
|
|
style={{ left: position.x, top: position.y }}
|
|
>
|
|
<ElementEditorHeader
|
|
title={title}
|
|
isCollapsed={isCollapsed}
|
|
showRemoveButton={Boolean(selectedElement)}
|
|
onToggleCollapse={onToggleCollapse}
|
|
onRemove={removeSelectedElement}
|
|
onDragStart={onDragStart}
|
|
/>
|
|
|
|
{!isCollapsed && (
|
|
<>
|
|
{/* Background Image Settings */}
|
|
{selectedMenuItem === 'background_image' && (
|
|
<BackgroundSettingsEditor
|
|
type='image'
|
|
value={pageBackground.imageUrl}
|
|
options={assetOptions.backgroundImage}
|
|
onChange={(value) => {
|
|
setBackgroundImageUrl(value);
|
|
if (value) setBackgroundVideoUrl('');
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{/* Background Video Settings */}
|
|
{selectedMenuItem === 'background_video' && (
|
|
<BackgroundSettingsEditor
|
|
type='video'
|
|
value={pageBackground.videoUrl}
|
|
options={assetOptions.video}
|
|
durationNote={durationNotes.backgroundVideo}
|
|
onChange={(value) => {
|
|
setBackgroundVideoUrl(value);
|
|
if (value) setBackgroundImageUrl('');
|
|
}}
|
|
videoAutoplay={pageBackground.videoSettings.autoplay}
|
|
videoLoop={pageBackground.videoSettings.loop}
|
|
videoMuted={pageBackground.videoSettings.muted}
|
|
videoStartTime={pageBackground.videoSettings.startTime}
|
|
videoEndTime={pageBackground.videoSettings.endTime}
|
|
onVideoSettingsChange={setBackgroundVideoSettings}
|
|
/>
|
|
)}
|
|
|
|
{/* Background Audio Settings */}
|
|
{selectedMenuItem === 'background_audio' && (
|
|
<BackgroundSettingsEditor
|
|
type='audio'
|
|
value={pageBackground.audioUrl}
|
|
options={assetOptions.audio}
|
|
durationNote={durationNotes.backgroundAudio}
|
|
onChange={setBackgroundAudioUrl}
|
|
/>
|
|
)}
|
|
|
|
{/* Create Transition Form */}
|
|
{selectedMenuItem === 'create_transition' && (
|
|
<CreateTransitionForm
|
|
name={transitionCreation.name}
|
|
videoUrl={transitionCreation.videoUrl}
|
|
supportsReverse={transitionCreation.supportsReverse}
|
|
videoOptions={assetOptions.transitionVideo}
|
|
durationNote={durationNotes.newTransition}
|
|
isCreating={transitionCreation.isCreating}
|
|
onNameChange={transitionCreation.setName}
|
|
onVideoUrlChange={transitionCreation.setVideoUrl}
|
|
onSupportsReverseChange={transitionCreation.setSupportsReverse}
|
|
onSubmit={transitionCreation.create}
|
|
/>
|
|
)}
|
|
|
|
{/* Element Settings */}
|
|
{selectedElement && (
|
|
<>
|
|
<ElementSettingsTabsCompact
|
|
activeTab={activeTab}
|
|
onTabChange={(tab) =>
|
|
setActiveTab(tab as 'general' | 'css' | 'effects')
|
|
}
|
|
tabs={[
|
|
{ id: 'general', label: 'General' },
|
|
{ id: 'css', label: 'CSS' },
|
|
{ id: 'effects', label: 'Effects' },
|
|
]}
|
|
/>
|
|
|
|
{/* General Tab */}
|
|
{activeTab === 'general' && (
|
|
<>
|
|
<CommonSettingsSectionCompact
|
|
label={selectedElement.label}
|
|
xPercent={String(selectedElement.xPercent ?? 50)}
|
|
yPercent={String(selectedElement.yPercent ?? 50)}
|
|
appearDelaySec={String(selectedElement.appearDelaySec ?? 0)}
|
|
appearDurationSec={
|
|
selectedElement.appearDurationSec != null
|
|
? String(selectedElement.appearDurationSec)
|
|
: ''
|
|
}
|
|
showPosition={false}
|
|
onChange={(prop, value) => {
|
|
if (prop === 'label') {
|
|
updateSelectedElement({ label: value });
|
|
} else if (prop === 'appearDelaySec') {
|
|
updateSelectedElement({
|
|
appearDelaySec: normalizeAppearDelaySec(value),
|
|
});
|
|
} else if (prop === 'appearDurationSec') {
|
|
updateSelectedElement({
|
|
appearDurationSec: normalizeAppearDurationSec(value),
|
|
});
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{/* Navigation Settings */}
|
|
{isNavigationElementType(selectedElement.type) && (
|
|
<NavigationSettingsSectionCompact
|
|
type={
|
|
selectedElement.type as
|
|
| 'navigation_next'
|
|
| 'navigation_prev'
|
|
}
|
|
navType={selectedElement.navType}
|
|
navLabel={selectedElement.navLabel || ''}
|
|
navLabelFontFamily={
|
|
selectedElement.navLabelFontFamily || ''
|
|
}
|
|
navDisabled={selectedElement.navDisabled || false}
|
|
iconUrl={selectedElement.iconUrl || ''}
|
|
targetPageSlug={selectedElement.targetPageSlug || ''}
|
|
transitionVideoUrl={
|
|
selectedElement.transitionVideoUrl || ''
|
|
}
|
|
transitionReverseMode={
|
|
selectedElement.transitionReverseMode || 'auto_reverse'
|
|
}
|
|
reverseVideoUrl={selectedElement.reverseVideoUrl || ''}
|
|
transitionType={selectedElement.transitionType || ''}
|
|
transitionDurationMs={
|
|
selectedElement.transitionDurationMs ?? ''
|
|
}
|
|
transitionEasing={selectedElement.transitionEasing || ''}
|
|
transitionOverlayColor={
|
|
selectedElement.transitionOverlayColor || ''
|
|
}
|
|
allowedNavigationTypes={allowedNavigationTypes}
|
|
iconAssetOptions={assetOptions.icon}
|
|
transitionVideoOptions={assetOptions.transitionVideo}
|
|
pages={pages}
|
|
activePageId={activePageId || ''}
|
|
selectedMediaDurationNote={durationNotes.selectedMedia}
|
|
selectedTransitionDurationNote={
|
|
durationNotes.selectedTransition
|
|
}
|
|
onChange={(prop, value) => {
|
|
if (prop === 'type') {
|
|
const nextType = value as NavigationElementType;
|
|
updateSelectedElement(
|
|
normalizeNavigationType(selectedElement, nextType),
|
|
);
|
|
} else if (prop === 'transitionVideoUrl') {
|
|
const nextVideoUrl = value as string;
|
|
const resolvedDuration = getDuration(nextVideoUrl);
|
|
updateSelectedElement({
|
|
transitionVideoUrl: nextVideoUrl,
|
|
transitionDurationSec:
|
|
resolvedDuration || undefined,
|
|
});
|
|
} else if (prop === 'targetPageSlug') {
|
|
updateSelectedElement({
|
|
targetPageSlug: value as string,
|
|
targetPageId: '',
|
|
});
|
|
} else {
|
|
updateSelectedElement({
|
|
[prop]: value,
|
|
});
|
|
}
|
|
}}
|
|
onPreviewTransition={onPreviewTransition}
|
|
/>
|
|
)}
|
|
|
|
{/* Tooltip Settings */}
|
|
{isTooltipElementType(selectedElement.type) && (
|
|
<TooltipSettingsSectionCompact
|
|
iconUrl={selectedElement.iconUrl || ''}
|
|
tooltipTitle={selectedElement.tooltipTitle || ''}
|
|
tooltipText={selectedElement.tooltipText || ''}
|
|
tooltipTitleFontFamily={
|
|
selectedElement.tooltipTitleFontFamily || ''
|
|
}
|
|
tooltipTextFontFamily={
|
|
selectedElement.tooltipTextFontFamily || ''
|
|
}
|
|
iconAssetOptions={assetOptions.icon}
|
|
onChange={(prop, value) =>
|
|
updateSelectedElement({ [prop]: value })
|
|
}
|
|
/>
|
|
)}
|
|
|
|
{/* Description Settings */}
|
|
{isDescriptionElementType(selectedElement.type) && (
|
|
<DescriptionSettingsSectionCompact
|
|
iconUrl={selectedElement.iconUrl || ''}
|
|
descriptionTitle={selectedElement.descriptionTitle || ''}
|
|
descriptionText={selectedElement.descriptionText || ''}
|
|
descriptionTitleFontSize={
|
|
selectedElement.descriptionTitleFontSize || '48px'
|
|
}
|
|
descriptionTextFontSize={
|
|
selectedElement.descriptionTextFontSize || '36px'
|
|
}
|
|
descriptionTitleFontFamily={
|
|
selectedElement.descriptionTitleFontFamily || 'inherit'
|
|
}
|
|
descriptionTextFontFamily={
|
|
selectedElement.descriptionTextFontFamily || 'inherit'
|
|
}
|
|
descriptionTitleColor={
|
|
selectedElement.descriptionTitleColor || '#000000'
|
|
}
|
|
descriptionTextColor={
|
|
selectedElement.descriptionTextColor || '#4B5563'
|
|
}
|
|
iconAssetOptions={assetOptions.icon}
|
|
onChange={(prop, value) =>
|
|
updateSelectedElement({ [prop]: value })
|
|
}
|
|
/>
|
|
)}
|
|
|
|
{/* Media Settings */}
|
|
{isMediaElementType(selectedElement.type) && (
|
|
<MediaSettingsSectionCompact
|
|
mediaType={
|
|
isVideoPlayerElementType(selectedElement.type)
|
|
? 'video'
|
|
: 'audio'
|
|
}
|
|
mediaUrl={selectedElement.mediaUrl || ''}
|
|
mediaAutoplay={Boolean(selectedElement.mediaAutoplay)}
|
|
mediaLoop={Boolean(selectedElement.mediaLoop)}
|
|
mediaMuted={Boolean(selectedElement.mediaMuted)}
|
|
videoAssetOptions={assetOptions.video}
|
|
audioAssetOptions={assetOptions.audio}
|
|
onChange={(prop, value) =>
|
|
updateSelectedElement({ [prop]: value })
|
|
}
|
|
/>
|
|
)}
|
|
|
|
{/* Gallery Settings */}
|
|
{isGalleryElementType(selectedElement.type) && (
|
|
<>
|
|
<GallerySettingsSectionCompact
|
|
galleryHeaderImageUrl={
|
|
selectedElement.galleryHeaderImageUrl || ''
|
|
}
|
|
galleryHeaderText={
|
|
selectedElement.galleryHeaderText || ''
|
|
}
|
|
galleryTitle={selectedElement.galleryTitle || ''}
|
|
galleryInfoSpans={
|
|
selectedElement.galleryInfoSpans || []
|
|
}
|
|
galleryCards={selectedElement.galleryCards || []}
|
|
imageAssetOptions={assetOptions.image}
|
|
iconAssetOptions={assetOptions.icon}
|
|
onUpdateHeader={(patch) => updateSelectedElement(patch)}
|
|
onAddInfoSpan={galleryInfoSpans.add}
|
|
onUpdateInfoSpan={galleryInfoSpans.update}
|
|
onRemoveInfoSpan={galleryInfoSpans.remove}
|
|
onAddCard={galleryCards.add}
|
|
onUpdateCard={galleryCards.update}
|
|
onRemoveCard={galleryCards.remove}
|
|
/>
|
|
<GalleryCarouselSettingsSectionCompact
|
|
prevIconUrl={
|
|
selectedElement.galleryCarouselPrevIconUrl || ''
|
|
}
|
|
nextIconUrl={
|
|
selectedElement.galleryCarouselNextIconUrl || ''
|
|
}
|
|
backIconUrl={
|
|
selectedElement.galleryCarouselBackIconUrl || ''
|
|
}
|
|
backLabel={
|
|
selectedElement.galleryCarouselBackLabel || ''
|
|
}
|
|
prevWidth={
|
|
selectedElement.galleryCarouselPrevWidth || ''
|
|
}
|
|
prevHeight={
|
|
selectedElement.galleryCarouselPrevHeight || ''
|
|
}
|
|
nextWidth={
|
|
selectedElement.galleryCarouselNextWidth || ''
|
|
}
|
|
nextHeight={
|
|
selectedElement.galleryCarouselNextHeight || ''
|
|
}
|
|
backWidth={
|
|
selectedElement.galleryCarouselBackWidth || ''
|
|
}
|
|
backHeight={
|
|
selectedElement.galleryCarouselBackHeight || ''
|
|
}
|
|
iconAssetOptions={assetOptions.icon}
|
|
onUpdateElement={updateSelectedElement}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{/* Carousel Settings */}
|
|
{isCarouselElementType(selectedElement.type) && (
|
|
<CarouselSettingsSectionCompact
|
|
carouselSlides={selectedElement.carouselSlides || []}
|
|
carouselPrevIconUrl={
|
|
selectedElement.carouselPrevIconUrl || ''
|
|
}
|
|
carouselNextIconUrl={
|
|
selectedElement.carouselNextIconUrl || ''
|
|
}
|
|
carouselCaptionFontFamily={
|
|
selectedElement.carouselCaptionFontFamily || ''
|
|
}
|
|
carouselFullWidth={
|
|
selectedElement.carouselFullWidth || false
|
|
}
|
|
carouselPrevWidth={
|
|
selectedElement.carouselPrevWidth || ''
|
|
}
|
|
carouselPrevHeight={
|
|
selectedElement.carouselPrevHeight || ''
|
|
}
|
|
carouselNextWidth={
|
|
selectedElement.carouselNextWidth || ''
|
|
}
|
|
carouselNextHeight={
|
|
selectedElement.carouselNextHeight || ''
|
|
}
|
|
iconAssetOptions={assetOptions.icon}
|
|
imageAssetOptions={assetOptions.image}
|
|
onUpdateElement={updateSelectedElement}
|
|
onAddSlide={carouselSlides.add}
|
|
onUpdateSlide={carouselSlides.update}
|
|
onRemoveSlide={carouselSlides.remove}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* CSS Styles Tab */}
|
|
{activeTab === 'css' && (
|
|
<>
|
|
{/* Gallery Section Styles (shown first for gallery elements) */}
|
|
{isGalleryElementType(selectedElement.type) && (
|
|
<div className='space-y-2 mb-4'>
|
|
<p className='text-[11px] font-semibold text-white/90'>
|
|
Gallery Section Styles
|
|
</p>
|
|
<GallerySectionStyleInputs
|
|
sectionLabel='Header'
|
|
prefix='galleryHeader'
|
|
values={{
|
|
galleryHeaderBackgroundColor:
|
|
selectedElement.galleryHeaderBackgroundColor || '',
|
|
galleryHeaderColor:
|
|
selectedElement.galleryHeaderColor || '',
|
|
galleryHeaderFontFamily:
|
|
selectedElement.galleryHeaderFontFamily || '',
|
|
galleryHeaderFontSize:
|
|
selectedElement.galleryHeaderFontSize || '',
|
|
galleryHeaderFontWeight:
|
|
selectedElement.galleryHeaderFontWeight || '',
|
|
galleryHeaderPadding:
|
|
selectedElement.galleryHeaderPadding || '',
|
|
galleryHeaderBorderRadius:
|
|
selectedElement.galleryHeaderBorderRadius || '',
|
|
galleryHeaderBorder:
|
|
selectedElement.galleryHeaderBorder || '',
|
|
galleryHeaderWidth:
|
|
selectedElement.galleryHeaderWidth || '',
|
|
galleryHeaderHeight:
|
|
selectedElement.galleryHeaderHeight || '',
|
|
galleryHeaderMinHeight:
|
|
selectedElement.galleryHeaderMinHeight || '',
|
|
galleryHeaderMaxHeight:
|
|
selectedElement.galleryHeaderMaxHeight || '',
|
|
galleryHeaderTextAlign:
|
|
selectedElement.galleryHeaderTextAlign || 'center',
|
|
}}
|
|
onChange={(prop, value) =>
|
|
updateSelectedElement({ [prop]: value || undefined })
|
|
}
|
|
showFont
|
|
showDimensions
|
|
showTextAlign
|
|
/>
|
|
<GallerySectionStyleInputs
|
|
sectionLabel='Title'
|
|
prefix='galleryTitle'
|
|
values={{
|
|
galleryTitleBackgroundColor:
|
|
selectedElement.galleryTitleBackgroundColor || '',
|
|
galleryTitleColor:
|
|
selectedElement.galleryTitleColor || '',
|
|
galleryTitleFontFamily:
|
|
selectedElement.galleryTitleFontFamily || '',
|
|
galleryTitleFontSize:
|
|
selectedElement.galleryTitleFontSize || '',
|
|
galleryTitleFontWeight:
|
|
selectedElement.galleryTitleFontWeight || '',
|
|
galleryTitlePadding:
|
|
selectedElement.galleryTitlePadding || '',
|
|
galleryTitleBorderRadius:
|
|
selectedElement.galleryTitleBorderRadius || '',
|
|
galleryTitleBorder:
|
|
selectedElement.galleryTitleBorder || '',
|
|
galleryTitleTextAlign:
|
|
selectedElement.galleryTitleTextAlign || 'center',
|
|
}}
|
|
onChange={(prop, value) =>
|
|
updateSelectedElement({ [prop]: value || undefined })
|
|
}
|
|
showFont
|
|
showTextAlign
|
|
/>
|
|
<GallerySectionStyleInputs
|
|
sectionLabel='Info Spans'
|
|
prefix='gallerySpan'
|
|
values={{
|
|
gallerySpanBackgroundColor:
|
|
selectedElement.gallerySpanBackgroundColor || '',
|
|
gallerySpanColor:
|
|
selectedElement.gallerySpanColor || '',
|
|
gallerySpanFontFamily:
|
|
selectedElement.gallerySpanFontFamily || '',
|
|
gallerySpanFontSize:
|
|
selectedElement.gallerySpanFontSize || '',
|
|
gallerySpanFontWeight:
|
|
selectedElement.gallerySpanFontWeight || '',
|
|
gallerySpanPadding:
|
|
selectedElement.gallerySpanPadding || '',
|
|
gallerySpanBorderRadius:
|
|
selectedElement.gallerySpanBorderRadius || '',
|
|
gallerySpanBorder:
|
|
selectedElement.gallerySpanBorder || '',
|
|
gallerySpanGap: selectedElement.gallerySpanGap || '',
|
|
gallerySpanColumns:
|
|
selectedElement.gallerySpanColumns ||
|
|
selectedElement.galleryColumns ||
|
|
3,
|
|
gallerySpanTextAlign:
|
|
selectedElement.gallerySpanTextAlign || 'center',
|
|
}}
|
|
onChange={(prop, value) =>
|
|
updateSelectedElement({ [prop]: value || undefined })
|
|
}
|
|
showFont
|
|
showGap
|
|
showColumns
|
|
showTextAlign
|
|
/>
|
|
<GallerySectionStyleInputs
|
|
sectionLabel='Image Cards'
|
|
prefix='galleryCard'
|
|
values={{
|
|
galleryCardBackgroundColor:
|
|
selectedElement.galleryCardBackgroundColor || '',
|
|
galleryCardBorderRadius:
|
|
selectedElement.galleryCardBorderRadius || '',
|
|
galleryCardBorder:
|
|
selectedElement.galleryCardBorder || '',
|
|
galleryCardGap: selectedElement.galleryCardGap || '',
|
|
galleryCardColumns:
|
|
selectedElement.galleryCardColumns ||
|
|
selectedElement.galleryColumns ||
|
|
3,
|
|
galleryCardTitleColor:
|
|
selectedElement.galleryCardTitleColor || '',
|
|
galleryCardTitleBackgroundColor:
|
|
selectedElement.galleryCardTitleBackgroundColor ||
|
|
'',
|
|
galleryCardTitleFontSize:
|
|
selectedElement.galleryCardTitleFontSize || '',
|
|
galleryCardTitleFontWeight:
|
|
selectedElement.galleryCardTitleFontWeight || '',
|
|
galleryCardTitleShadow:
|
|
selectedElement.galleryCardTitleShadow || '',
|
|
galleryCardAspectRatio:
|
|
selectedElement.galleryCardAspectRatio || '',
|
|
galleryCardMinHeight:
|
|
selectedElement.galleryCardMinHeight || '',
|
|
}}
|
|
onChange={(prop, value) =>
|
|
updateSelectedElement({ [prop]: value || undefined })
|
|
}
|
|
showGap
|
|
showColumns
|
|
showTitleStyles
|
|
showAspectRatio
|
|
/>
|
|
<p className='text-[11px] font-semibold text-white/90 pt-2'>
|
|
General Element Styles
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<StyleSettingsSectionCompact
|
|
values={{
|
|
width: extractNumericValue(selectedElement.width),
|
|
height: extractNumericValue(selectedElement.height),
|
|
minWidth: extractNumericValue(selectedElement.minWidth),
|
|
maxWidth: extractNumericValue(selectedElement.maxWidth),
|
|
minHeight: extractNumericValue(selectedElement.minHeight),
|
|
maxHeight: extractNumericValue(selectedElement.maxHeight),
|
|
margin: selectedElement.margin || '',
|
|
padding: selectedElement.padding || '',
|
|
gap: extractNumericValue(selectedElement.gap),
|
|
fontSize: selectedElement.fontSize || '',
|
|
lineHeight: selectedElement.lineHeight || '',
|
|
fontWeight: selectedElement.fontWeight || '',
|
|
border: extractNumericValue(selectedElement.border),
|
|
borderRadius: extractNumericValue(
|
|
selectedElement.borderRadius,
|
|
),
|
|
opacity: selectedElement.opacity || '',
|
|
boxShadow: selectedElement.boxShadow || '',
|
|
display: selectedElement.display || '',
|
|
position: selectedElement.position || '',
|
|
justifyContent: selectedElement.justifyContent || '',
|
|
alignItems: selectedElement.alignItems || '',
|
|
textAlign: selectedElement.textAlign || '',
|
|
zIndex: selectedElement.zIndex || '',
|
|
backgroundColor: selectedElement.backgroundColor || '',
|
|
color: selectedElement.color || '',
|
|
}}
|
|
onChange={(prop, value) =>
|
|
handleCssPropertyChange(
|
|
prop,
|
|
value,
|
|
updateSelectedElement,
|
|
)
|
|
}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{/* Effects Tab */}
|
|
{activeTab === 'effects' && (
|
|
<EffectsSettingsSectionCompact
|
|
elementType={selectedElement.type}
|
|
values={{
|
|
appearAnimation: selectedElement.appearAnimation || '',
|
|
appearAnimationDuration:
|
|
selectedElement.appearAnimationDuration || '',
|
|
appearAnimationEasing:
|
|
selectedElement.appearAnimationEasing || '',
|
|
hoverScale: selectedElement.hoverScale || '',
|
|
hoverOpacity: selectedElement.hoverOpacity || '',
|
|
hoverBackgroundColor:
|
|
selectedElement.hoverBackgroundColor || '',
|
|
hoverColor: selectedElement.hoverColor || '',
|
|
hoverBoxShadow: selectedElement.hoverBoxShadow || '',
|
|
hoverTransitionDuration:
|
|
selectedElement.hoverTransitionDuration || '',
|
|
focusScale: selectedElement.focusScale || '',
|
|
focusOpacity: selectedElement.focusOpacity || '',
|
|
focusOutline: selectedElement.focusOutline || '',
|
|
focusBoxShadow: selectedElement.focusBoxShadow || '',
|
|
activeScale: selectedElement.activeScale || '',
|
|
activeOpacity: selectedElement.activeOpacity || '',
|
|
activeBackgroundColor:
|
|
selectedElement.activeBackgroundColor || '',
|
|
// Slide transition values (gallery/carousel)
|
|
slideTransitionType:
|
|
selectedElement.type === 'gallery'
|
|
? selectedElement.gallerySlideTransitionType || ''
|
|
: selectedElement.carouselSlideTransitionType || '',
|
|
slideTransitionDurationMs:
|
|
selectedElement.type === 'gallery'
|
|
? selectedElement.gallerySlideTransitionDurationMs !==
|
|
undefined &&
|
|
selectedElement.gallerySlideTransitionDurationMs !==
|
|
''
|
|
? String(
|
|
selectedElement.gallerySlideTransitionDurationMs,
|
|
)
|
|
: ''
|
|
: selectedElement.carouselSlideTransitionDurationMs !==
|
|
undefined &&
|
|
selectedElement.carouselSlideTransitionDurationMs !==
|
|
''
|
|
? String(
|
|
selectedElement.carouselSlideTransitionDurationMs,
|
|
)
|
|
: '',
|
|
slideTransitionEasing:
|
|
selectedElement.type === 'gallery'
|
|
? selectedElement.gallerySlideTransitionEasing || ''
|
|
: selectedElement.carouselSlideTransitionEasing || '',
|
|
slideTransitionOverlayColor:
|
|
selectedElement.type === 'gallery'
|
|
? selectedElement.gallerySlideTransitionOverlayColor ||
|
|
''
|
|
: selectedElement.carouselSlideTransitionOverlayColor ||
|
|
'',
|
|
}}
|
|
onChange={(prop, value) => {
|
|
// Handle slide transition properties with proper prefixes
|
|
if (prop === 'slideTransitionType') {
|
|
const typedValue = (value || undefined) as
|
|
| 'fade'
|
|
| 'none'
|
|
| ''
|
|
| undefined;
|
|
if (selectedElement.type === 'gallery') {
|
|
updateSelectedElement({
|
|
gallerySlideTransitionType: typedValue,
|
|
});
|
|
} else if (selectedElement.type === 'carousel') {
|
|
updateSelectedElement({
|
|
carouselSlideTransitionType: typedValue,
|
|
});
|
|
}
|
|
} else if (prop === 'slideTransitionDurationMs') {
|
|
const ms = value ? parseInt(value, 10) : undefined;
|
|
const typedMs = ms !== undefined && ms > 0 ? ms : '';
|
|
if (selectedElement.type === 'gallery') {
|
|
updateSelectedElement({
|
|
gallerySlideTransitionDurationMs: typedMs,
|
|
});
|
|
} else if (selectedElement.type === 'carousel') {
|
|
updateSelectedElement({
|
|
carouselSlideTransitionDurationMs: typedMs,
|
|
});
|
|
}
|
|
} else if (prop === 'slideTransitionEasing') {
|
|
// Cast to proper type - form values are validated by select options
|
|
type EasingValue =
|
|
| 'ease-in-out'
|
|
| 'ease-in'
|
|
| 'ease-out'
|
|
| 'linear'
|
|
| ''
|
|
| undefined;
|
|
const typedEasing = (value || undefined) as EasingValue;
|
|
if (selectedElement.type === 'gallery') {
|
|
updateSelectedElement({
|
|
gallerySlideTransitionEasing: typedEasing,
|
|
});
|
|
} else if (selectedElement.type === 'carousel') {
|
|
updateSelectedElement({
|
|
carouselSlideTransitionEasing: typedEasing,
|
|
});
|
|
}
|
|
} else if (prop === 'slideTransitionOverlayColor') {
|
|
if (selectedElement.type === 'gallery') {
|
|
updateSelectedElement({
|
|
gallerySlideTransitionOverlayColor:
|
|
value || undefined,
|
|
});
|
|
} else if (selectedElement.type === 'carousel') {
|
|
updateSelectedElement({
|
|
carouselSlideTransitionOverlayColor:
|
|
value || undefined,
|
|
});
|
|
}
|
|
} else {
|
|
// Standard effect properties
|
|
updateSelectedElement({
|
|
[prop]: value || undefined,
|
|
});
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default ElementEditorPanel;
|