added additional settings for gallery, made structure more flexible
This commit is contained in:
parent
73f524dab3
commit
8ef30576b1
File diff suppressed because one or more lines are too long
@ -17,6 +17,7 @@ import {
|
|||||||
GallerySettingsSectionCompact,
|
GallerySettingsSectionCompact,
|
||||||
CarouselSettingsSectionCompact,
|
CarouselSettingsSectionCompact,
|
||||||
GalleryCarouselSettingsSectionCompact,
|
GalleryCarouselSettingsSectionCompact,
|
||||||
|
GallerySectionStyleInputs,
|
||||||
extractNumericValue,
|
extractNumericValue,
|
||||||
} from '../ElementSettings';
|
} from '../ElementSettings';
|
||||||
import BackgroundSettingsEditor from './BackgroundSettingsEditor';
|
import BackgroundSettingsEditor from './BackgroundSettingsEditor';
|
||||||
@ -132,7 +133,7 @@ interface ElementEditorPanelProps {
|
|||||||
};
|
};
|
||||||
galleryInfoSpans: {
|
galleryInfoSpans: {
|
||||||
add: () => void;
|
add: () => void;
|
||||||
update: (spanId: string, text: string) => void;
|
update: (spanId: string, patch: Partial<GalleryInfoSpan>) => void;
|
||||||
remove: (spanId: string) => void;
|
remove: (spanId: string) => void;
|
||||||
};
|
};
|
||||||
carouselSlides: {
|
carouselSlides: {
|
||||||
@ -502,15 +503,9 @@ export function ElementEditorPanel({
|
|||||||
galleryInfoSpans={
|
galleryInfoSpans={
|
||||||
selectedElement.galleryInfoSpans || []
|
selectedElement.galleryInfoSpans || []
|
||||||
}
|
}
|
||||||
galleryColumns={selectedElement.galleryColumns || 3}
|
|
||||||
galleryTitleFontFamily={
|
|
||||||
selectedElement.galleryTitleFontFamily || ''
|
|
||||||
}
|
|
||||||
galleryTextFontFamily={
|
|
||||||
selectedElement.galleryTextFontFamily || ''
|
|
||||||
}
|
|
||||||
galleryCards={selectedElement.galleryCards || []}
|
galleryCards={selectedElement.galleryCards || []}
|
||||||
imageAssetOptions={imageAssetOptions}
|
imageAssetOptions={imageAssetOptions}
|
||||||
|
iconAssetOptions={iconAssetOptions}
|
||||||
onUpdateHeader={(patch) => onUpdateElement(patch)}
|
onUpdateHeader={(patch) => onUpdateElement(patch)}
|
||||||
onAddInfoSpan={galleryInfoSpans.add}
|
onAddInfoSpan={galleryInfoSpans.add}
|
||||||
onUpdateInfoSpan={galleryInfoSpans.update}
|
onUpdateInfoSpan={galleryInfoSpans.update}
|
||||||
@ -581,6 +576,141 @@ export function ElementEditorPanel({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* CSS Styles Tab */}
|
{/* 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-gray-700'>
|
||||||
|
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 || '',
|
||||||
|
}}
|
||||||
|
onChange={(prop, value) =>
|
||||||
|
onUpdateElement({ [prop]: value || undefined })
|
||||||
|
}
|
||||||
|
showFont
|
||||||
|
/>
|
||||||
|
<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 || '',
|
||||||
|
}}
|
||||||
|
onChange={(prop, value) =>
|
||||||
|
onUpdateElement({ [prop]: value || undefined })
|
||||||
|
}
|
||||||
|
showFont
|
||||||
|
/>
|
||||||
|
<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,
|
||||||
|
}}
|
||||||
|
onChange={(prop, value) =>
|
||||||
|
onUpdateElement({ [prop]: value || undefined })
|
||||||
|
}
|
||||||
|
showFont
|
||||||
|
showGap
|
||||||
|
showColumns
|
||||||
|
/>
|
||||||
|
<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 || '',
|
||||||
|
}}
|
||||||
|
onChange={(prop, value) =>
|
||||||
|
onUpdateElement({ [prop]: value || undefined })
|
||||||
|
}
|
||||||
|
showGap
|
||||||
|
showColumns
|
||||||
|
showTitleStyles
|
||||||
|
/>
|
||||||
|
<p className='text-[11px] font-semibold text-gray-700 pt-2'>
|
||||||
|
General Element Styles
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{activeTab === 'css' && (
|
{activeTab === 'css' && (
|
||||||
<StyleSettingsSectionCompact
|
<StyleSettingsSectionCompact
|
||||||
values={{
|
values={{
|
||||||
|
|||||||
@ -0,0 +1,244 @@
|
|||||||
|
/**
|
||||||
|
* GallerySectionStyleInputs
|
||||||
|
*
|
||||||
|
* Reusable component for gallery section style inputs.
|
||||||
|
* Used in the CSS tab to configure wrapper, header, title, span, and card styles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { FONT_OPTIONS } from '../../lib/fonts';
|
||||||
|
|
||||||
|
interface GallerySectionStyleInputsProps {
|
||||||
|
sectionLabel: string;
|
||||||
|
prefix: string;
|
||||||
|
values: Record<string, string | number>;
|
||||||
|
onChange: (prop: string, value: string | number) => void;
|
||||||
|
showFont?: boolean;
|
||||||
|
showColumns?: boolean;
|
||||||
|
showGap?: boolean;
|
||||||
|
showBlur?: boolean;
|
||||||
|
showTitleStyles?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable inputs for gallery section styling
|
||||||
|
*/
|
||||||
|
const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||||
|
sectionLabel,
|
||||||
|
prefix,
|
||||||
|
values,
|
||||||
|
onChange,
|
||||||
|
showFont = false,
|
||||||
|
showColumns = false,
|
||||||
|
showGap = false,
|
||||||
|
showBlur = false,
|
||||||
|
showTitleStyles = false,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
||||||
|
<p className='text-[11px] font-semibold text-gray-700'>{sectionLabel}</p>
|
||||||
|
|
||||||
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
|
{/* Background Color */}
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>BG color</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}BackgroundColor`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}BackgroundColor`, e.target.value)}
|
||||||
|
placeholder='rgba(0,0,0,0.6)'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Text Color (not for wrapper) */}
|
||||||
|
{prefix !== 'galleryWrapper' && !showTitleStyles && (
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Text color</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}Color`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}Color`, e.target.value)}
|
||||||
|
placeholder='#ffffff'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Padding */}
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Padding</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}Padding`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}Padding`, e.target.value)}
|
||||||
|
placeholder='0.75rem'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Border Radius */}
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Radius</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}BorderRadius`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}BorderRadius`, e.target.value)}
|
||||||
|
placeholder='0.75rem'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Border */}
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Border</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}Border`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}Border`, e.target.value)}
|
||||||
|
placeholder='1px solid #ccc'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Gap (optional) */}
|
||||||
|
{showGap && (
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Gap</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}Gap`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}Gap`, e.target.value)}
|
||||||
|
placeholder='0.5rem'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Backdrop Blur (wrapper only) */}
|
||||||
|
{showBlur && (
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Blur</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}BackdropBlur`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}BackdropBlur`, e.target.value)}
|
||||||
|
placeholder='4px'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Grid Columns (optional) */}
|
||||||
|
{showColumns && (
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Columns</label>
|
||||||
|
<input
|
||||||
|
type='number'
|
||||||
|
min='1'
|
||||||
|
max='6'
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}Columns`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}Columns`, parseInt(e.target.value) || 3)}
|
||||||
|
placeholder='3'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Font Size (optional) */}
|
||||||
|
{showFont && (
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Font size</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}FontSize`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}FontSize`, e.target.value)}
|
||||||
|
placeholder='0.875rem'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Font Weight (optional) */}
|
||||||
|
{showFont && (
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Font weight</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}FontWeight`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}FontWeight`, e.target.value)}
|
||||||
|
placeholder='500'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Font Family (optional - full width) */}
|
||||||
|
{showFont && (
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Font</label>
|
||||||
|
<select
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}FontFamily`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}FontFamily`, e.target.value)}
|
||||||
|
>
|
||||||
|
<option value=''>Not set</option>
|
||||||
|
{FONT_OPTIONS.map((font) => (
|
||||||
|
<option key={font.key} value={font.key}>
|
||||||
|
{font.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Card Title Styles (cards only) */}
|
||||||
|
{showTitleStyles && (
|
||||||
|
<>
|
||||||
|
<p className='text-[10px] text-gray-500 pt-1'>Card title overlay:</p>
|
||||||
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Title color</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values.galleryCardTitleColor || ''}
|
||||||
|
onChange={(e) => onChange('galleryCardTitleColor', e.target.value)}
|
||||||
|
placeholder='#ffffff'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Title BG</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values.galleryCardTitleBackgroundColor || ''}
|
||||||
|
onChange={(e) => onChange('galleryCardTitleBackgroundColor', e.target.value)}
|
||||||
|
placeholder='transparent'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Title size</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values.galleryCardTitleFontSize || ''}
|
||||||
|
onChange={(e) => onChange('galleryCardTitleFontSize', e.target.value)}
|
||||||
|
placeholder='0.75rem'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Title weight</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values.galleryCardTitleFontWeight || ''}
|
||||||
|
onChange={(e) => onChange('galleryCardTitleFontWeight', e.target.value)}
|
||||||
|
placeholder='700'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>Title shadow</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values.galleryCardTitleShadow || ''}
|
||||||
|
onChange={(e) => onChange('galleryCardTitleShadow', e.target.value)}
|
||||||
|
placeholder='0 1px 3px rgba(0,0,0,0.5)'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GallerySectionStyleInputs;
|
||||||
@ -3,6 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Compact gallery element settings for constructor sidebar.
|
* Compact gallery element settings for constructor sidebar.
|
||||||
* Header image, title, info spans, and card management.
|
* Header image, title, info spans, and card management.
|
||||||
|
* Note: Fonts, columns, and styling are now in the CSS tab.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -12,7 +13,6 @@ import type {
|
|||||||
AssetOption,
|
AssetOption,
|
||||||
} from '../../types/constructor';
|
} from '../../types/constructor';
|
||||||
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
import { addFallbackAssetOption } from '../../lib/constructorHelpers';
|
||||||
import { FONT_OPTIONS } from '../../lib/fonts';
|
|
||||||
|
|
||||||
interface GallerySettingsSectionCompactProps {
|
interface GallerySettingsSectionCompactProps {
|
||||||
// Header settings
|
// Header settings
|
||||||
@ -20,24 +20,18 @@ interface GallerySettingsSectionCompactProps {
|
|||||||
galleryHeaderText: string;
|
galleryHeaderText: string;
|
||||||
galleryTitle: string;
|
galleryTitle: string;
|
||||||
galleryInfoSpans: GalleryInfoSpan[];
|
galleryInfoSpans: GalleryInfoSpan[];
|
||||||
galleryColumns: number;
|
|
||||||
// Font settings
|
|
||||||
galleryTitleFontFamily: string;
|
|
||||||
galleryTextFontFamily: string;
|
|
||||||
// Cards
|
// Cards
|
||||||
galleryCards: GalleryCard[];
|
galleryCards: GalleryCard[];
|
||||||
imageAssetOptions: AssetOption[];
|
imageAssetOptions: AssetOption[];
|
||||||
|
iconAssetOptions: AssetOption[];
|
||||||
// Header handlers
|
// Header handlers
|
||||||
onUpdateHeader: (patch: {
|
onUpdateHeader: (patch: {
|
||||||
galleryHeaderImageUrl?: string;
|
galleryHeaderImageUrl?: string;
|
||||||
galleryHeaderText?: string;
|
galleryHeaderText?: string;
|
||||||
galleryTitle?: string;
|
galleryTitle?: string;
|
||||||
galleryColumns?: number;
|
|
||||||
galleryTitleFontFamily?: string;
|
|
||||||
galleryTextFontFamily?: string;
|
|
||||||
}) => void;
|
}) => void;
|
||||||
onAddInfoSpan: () => void;
|
onAddInfoSpan: () => void;
|
||||||
onUpdateInfoSpan: (spanId: string, text: string) => void;
|
onUpdateInfoSpan: (spanId: string, patch: Partial<GalleryInfoSpan>) => void;
|
||||||
onRemoveInfoSpan: (spanId: string) => void;
|
onRemoveInfoSpan: (spanId: string) => void;
|
||||||
// Card handlers
|
// Card handlers
|
||||||
onAddCard: () => void;
|
onAddCard: () => void;
|
||||||
@ -52,11 +46,9 @@ const GallerySettingsSectionCompact: React.FC<
|
|||||||
galleryHeaderText,
|
galleryHeaderText,
|
||||||
galleryTitle,
|
galleryTitle,
|
||||||
galleryInfoSpans,
|
galleryInfoSpans,
|
||||||
galleryColumns,
|
|
||||||
galleryTitleFontFamily,
|
|
||||||
galleryTextFontFamily,
|
|
||||||
galleryCards,
|
galleryCards,
|
||||||
imageAssetOptions,
|
imageAssetOptions,
|
||||||
|
iconAssetOptions,
|
||||||
onUpdateHeader,
|
onUpdateHeader,
|
||||||
onAddInfoSpan,
|
onAddInfoSpan,
|
||||||
onUpdateInfoSpan,
|
onUpdateInfoSpan,
|
||||||
@ -107,58 +99,6 @@ const GallerySettingsSectionCompact: React.FC<
|
|||||||
onUpdateHeader({ galleryTitle: event.target.value })
|
onUpdateHeader({ galleryTitle: event.target.value })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
<label className='text-[10px] text-gray-600'>Grid columns:</label>
|
|
||||||
<input
|
|
||||||
type='number'
|
|
||||||
min='1'
|
|
||||||
max='6'
|
|
||||||
className='w-16 rounded border border-gray-300 px-2 py-1 text-xs'
|
|
||||||
value={galleryColumns}
|
|
||||||
onChange={(event) =>
|
|
||||||
onUpdateHeader({
|
|
||||||
galleryColumns: Math.max(1, Math.min(6, parseInt(event.target.value) || 3)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className='text-[10px] text-gray-600'>Title font:</label>
|
|
||||||
<select
|
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
|
||||||
value={galleryTitleFontFamily}
|
|
||||||
onChange={(event) =>
|
|
||||||
onUpdateHeader({ galleryTitleFontFamily: event.target.value })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<option value=''>Not set</option>
|
|
||||||
{FONT_OPTIONS.map((font) => (
|
|
||||||
<option key={font.key} value={font.key}>
|
|
||||||
{font.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className='text-[10px] text-gray-600'>Text font:</label>
|
|
||||||
<select
|
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
|
||||||
value={galleryTextFontFamily}
|
|
||||||
onChange={(event) =>
|
|
||||||
onUpdateHeader({ galleryTextFontFamily: event.target.value })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<option value=''>Not set</option>
|
|
||||||
{FONT_OPTIONS.map((font) => (
|
|
||||||
<option key={font.key} value={font.key}>
|
|
||||||
{font.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Spans */}
|
{/* Info Spans */}
|
||||||
@ -175,26 +115,48 @@ const GallerySettingsSectionCompact: React.FC<
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{galleryInfoSpans.map((span, index) => (
|
{galleryInfoSpans.map((span, index) => (
|
||||||
<div key={span.id} className='flex items-center gap-1'>
|
<div key={span.id} className='space-y-1'>
|
||||||
<input
|
<div className='flex items-center gap-1'>
|
||||||
className='flex-1 rounded border border-gray-300 px-2 py-1 text-xs'
|
<input
|
||||||
placeholder={`Span ${index + 1}`}
|
className='flex-1 rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={span.text}
|
placeholder={`Span ${index + 1} text`}
|
||||||
onChange={(event) => onUpdateInfoSpan(span.id, event.target.value)}
|
value={span.text}
|
||||||
/>
|
onChange={(event) =>
|
||||||
<button
|
onUpdateInfoSpan(span.id, { text: event.target.value })
|
||||||
type='button'
|
}
|
||||||
className='text-xs text-red-600 hover:underline px-1'
|
/>
|
||||||
onClick={() => onRemoveInfoSpan(span.id)}
|
<button
|
||||||
|
type='button'
|
||||||
|
className='text-xs text-red-600 hover:underline px-1'
|
||||||
|
onClick={() => onRemoveInfoSpan(span.id)}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={span.iconUrl || ''}
|
||||||
|
onChange={(event) =>
|
||||||
|
onUpdateInfoSpan(span.id, { iconUrl: event.target.value })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
×
|
<option value=''>No icon (use text)</option>
|
||||||
</button>
|
{addFallbackAssetOption(
|
||||||
|
iconAssetOptions,
|
||||||
|
span.iconUrl,
|
||||||
|
`Current icon`,
|
||||||
|
).map((option) => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{galleryInfoSpans.length === 0 && (
|
{galleryInfoSpans.length === 0 && (
|
||||||
<p className='text-[10px] text-gray-500'>
|
<p className='text-[10px] text-gray-500'>
|
||||||
Add spans for brief notes (capacity, price, etc.)
|
Add spans for brief notes (capacity, price, icons, etc.)
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export { default as MediaSettingsSection } from './MediaSettingsSection';
|
|||||||
export { default as MediaSettingsSectionCompact } from './MediaSettingsSectionCompact';
|
export { default as MediaSettingsSectionCompact } from './MediaSettingsSectionCompact';
|
||||||
export { default as GallerySettingsSection } from './GallerySettingsSection';
|
export { default as GallerySettingsSection } from './GallerySettingsSection';
|
||||||
export { default as GallerySettingsSectionCompact } from './GallerySettingsSectionCompact';
|
export { default as GallerySettingsSectionCompact } from './GallerySettingsSectionCompact';
|
||||||
|
export { default as GallerySectionStyleInputs } from './GallerySectionStyleInputs';
|
||||||
export { default as CarouselSettingsSection } from './CarouselSettingsSection';
|
export { default as CarouselSettingsSection } from './CarouselSettingsSection';
|
||||||
export { default as CarouselSettingsSectionCompact } from './CarouselSettingsSectionCompact';
|
export { default as CarouselSettingsSectionCompact } from './CarouselSettingsSectionCompact';
|
||||||
export { default as GalleryCarouselSettingsSectionCompact } from './GalleryCarouselSettingsSectionCompact';
|
export { default as GalleryCarouselSettingsSectionCompact } from './GalleryCarouselSettingsSectionCompact';
|
||||||
|
|||||||
@ -65,33 +65,41 @@ const DescriptionElement: React.FC<DescriptionElementProps> = ({
|
|||||||
|
|
||||||
// Without icon: render styled text description
|
// Without icon: render styled text description
|
||||||
// Background color is controlled via CSS Styles tab (backgroundColor property)
|
// Background color is controlled via CSS Styles tab (backgroundColor property)
|
||||||
// fontWeight from CSS Styles tab is applied to both title and text
|
// Inheritable styles (color, fontSize, fontWeight) cascade from General Element Styles
|
||||||
const fontWeight = element.fontWeight || 'bold';
|
// 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 (
|
return (
|
||||||
<div className={className} style={style}>
|
<div className={className} style={style}>
|
||||||
<div className='p-4'>
|
<div className='p-4'>
|
||||||
<p
|
<p style={titleStyle}>
|
||||||
style={{
|
|
||||||
fontSize:
|
|
||||||
normalizePixelValue(element.descriptionTitleFontSize) || '24px',
|
|
||||||
color: element.descriptionTitleColor || '#ffffff',
|
|
||||||
fontWeight,
|
|
||||||
...titleFontStyle,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{element.descriptionTitle || ''}
|
{element.descriptionTitle || ''}
|
||||||
</p>
|
</p>
|
||||||
{element.descriptionText && (
|
{element.descriptionText && (
|
||||||
<p
|
<p style={textStyle}>
|
||||||
style={{
|
|
||||||
fontSize:
|
|
||||||
normalizePixelValue(element.descriptionTextFontSize) || '16px',
|
|
||||||
color: element.descriptionTextColor || '#ffffff',
|
|
||||||
fontWeight,
|
|
||||||
...textFontStyle,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{element.descriptionText}
|
{element.descriptionText}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* GalleryElement Component
|
* GalleryElement Component
|
||||||
*
|
*
|
||||||
* Gallery element with header image, title, info spans, and grid of image cards.
|
* Gallery element with header image, title, info spans, and grid of image cards.
|
||||||
* Renders with unified wrapper styling + content.
|
* Renders with unified wrapper styling + content using section-specific styles.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
@ -13,7 +13,16 @@ import type {
|
|||||||
GalleryInfoSpan,
|
GalleryInfoSpan,
|
||||||
} from '../../../types/constructor';
|
} from '../../../types/constructor';
|
||||||
import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl';
|
import { resolveAssetPlaybackUrl } from '../../../lib/assetUrl';
|
||||||
import { getFontByKey, getFontStyle } from '../../../lib/fonts';
|
import {
|
||||||
|
buildGalleryHeaderStyle,
|
||||||
|
buildGalleryTitleStyle,
|
||||||
|
buildGallerySpanStyle,
|
||||||
|
buildGallerySpanGridStyle,
|
||||||
|
buildGalleryCardStyle,
|
||||||
|
buildGalleryCardTitleStyle,
|
||||||
|
buildGalleryCardGridStyle,
|
||||||
|
GALLERY_SECTION_DEFAULTS,
|
||||||
|
} from '../../../lib/gallerySectionStyles';
|
||||||
|
|
||||||
interface GalleryElementProps {
|
interface GalleryElementProps {
|
||||||
element: CanvasElement;
|
element: CanvasElement;
|
||||||
@ -36,31 +45,59 @@ const GalleryElement: React.FC<GalleryElementProps> = ({
|
|||||||
const headerImageUrl = element.galleryHeaderImageUrl;
|
const headerImageUrl = element.galleryHeaderImageUrl;
|
||||||
const headerText = element.galleryHeaderText;
|
const headerText = element.galleryHeaderText;
|
||||||
const title = element.galleryTitle;
|
const title = element.galleryTitle;
|
||||||
const columns = element.galleryColumns || 3;
|
|
||||||
|
|
||||||
// Resolve font keys to full CSS styles (including fontStretch for condensed variants)
|
// Build section styles from element properties
|
||||||
const titleFontStyle = useMemo(() => {
|
const headerStyle = useMemo(() => buildGalleryHeaderStyle(element), [element]);
|
||||||
const fontKey = element.galleryTitleFontFamily;
|
const titleStyle = useMemo(() => buildGalleryTitleStyle(element), [element]);
|
||||||
if (!fontKey) return {};
|
const spanStyle = useMemo(() => buildGallerySpanStyle(element), [element]);
|
||||||
const font = getFontByKey(fontKey);
|
const spanGridStyle = useMemo(() => buildGallerySpanGridStyle(element), [element]);
|
||||||
return font ? getFontStyle(font) : { fontFamily: fontKey };
|
const cardStyle = useMemo(() => buildGalleryCardStyle(element), [element]);
|
||||||
}, [element.galleryTitleFontFamily]);
|
const cardTitleStyle = useMemo(() => buildGalleryCardTitleStyle(element), [element]);
|
||||||
|
const cardGridStyle = useMemo(() => buildGalleryCardGridStyle(element), [element]);
|
||||||
|
|
||||||
const textFontStyle = useMemo(() => {
|
// Build wrapper style from general element styles with gallery defaults
|
||||||
const fontKey = element.galleryTextFontFamily;
|
const wrapperDefaults = GALLERY_SECTION_DEFAULTS.wrapper;
|
||||||
if (!fontKey) return {};
|
const wrapperStyle: CSSProperties = useMemo(() => ({
|
||||||
const font = getFontByKey(fontKey);
|
display: 'flex',
|
||||||
return font ? getFontStyle(font) : { fontFamily: fontKey };
|
flexDirection: 'column',
|
||||||
}, [element.galleryTextFontFamily]);
|
backgroundColor: style.backgroundColor || wrapperDefaults.backgroundColor,
|
||||||
|
padding: style.padding || wrapperDefaults.padding,
|
||||||
|
borderRadius: style.borderRadius || wrapperDefaults.borderRadius,
|
||||||
|
border: style.border,
|
||||||
|
gap: style.gap || wrapperDefaults.gap,
|
||||||
|
backdropFilter: wrapperDefaults.backdropFilter,
|
||||||
|
WebkitBackdropFilter: wrapperDefaults.backdropFilter,
|
||||||
|
// Visual properties that should apply to the wrapper, not outer positioning div
|
||||||
|
boxShadow: style.boxShadow,
|
||||||
|
opacity: style.opacity,
|
||||||
|
// Inheritable text styles - cascade to child sections
|
||||||
|
color: style.color,
|
||||||
|
fontSize: style.fontSize,
|
||||||
|
fontWeight: style.fontWeight,
|
||||||
|
lineHeight: style.lineHeight,
|
||||||
|
}), [style.backgroundColor, style.padding, style.borderRadius, style.border, style.gap, style.boxShadow, style.opacity, style.color, style.fontSize, style.fontWeight, style.lineHeight, wrapperDefaults]);
|
||||||
|
|
||||||
// Extract backgroundColor from style to apply to inner content wrapper
|
// Extract wrapper-related styles from outer style (they go to wrapper, not outer div)
|
||||||
const { backgroundColor, ...outerStyle } = style;
|
const {
|
||||||
|
backgroundColor: _bg,
|
||||||
|
padding: _pad,
|
||||||
|
borderRadius: _radius,
|
||||||
|
border: _border,
|
||||||
|
gap: _gap,
|
||||||
|
boxShadow: _shadow,
|
||||||
|
opacity: _opacity,
|
||||||
|
color: _color,
|
||||||
|
fontSize: _fontSize,
|
||||||
|
fontWeight: _fontWeight,
|
||||||
|
lineHeight: _lineHeight,
|
||||||
|
...outerStyle
|
||||||
|
} = style;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} style={outerStyle}>
|
<div className={className} style={outerStyle}>
|
||||||
<div
|
<div
|
||||||
className='flex flex-col gap-2 p-3 rounded-xl min-w-[200px] backdrop-blur-sm'
|
className='min-w-[200px]'
|
||||||
style={{ backgroundColor: backgroundColor || 'rgba(0, 0, 0, 0.6)' }}
|
style={wrapperStyle}
|
||||||
>
|
>
|
||||||
{/* Header: image takes priority, otherwise render text */}
|
{/* Header: image takes priority, otherwise render text */}
|
||||||
{headerImageUrl ? (
|
{headerImageUrl ? (
|
||||||
@ -72,56 +109,60 @@ const GalleryElement: React.FC<GalleryElementProps> = ({
|
|||||||
draggable={false}
|
draggable={false}
|
||||||
/>
|
/>
|
||||||
) : headerText ? (
|
) : headerText ? (
|
||||||
<div
|
<div style={headerStyle}>{headerText}</div>
|
||||||
className='text-2xl font-bold px-1 py-2'
|
|
||||||
style={titleFontStyle}
|
|
||||||
>
|
|
||||||
{headerText}
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
{title && (
|
{title && (
|
||||||
<div
|
<div className='text-center' style={titleStyle}>
|
||||||
className='bg-amber-50 text-slate-800 text-center py-2 px-3 rounded-lg font-semibold text-sm'
|
|
||||||
style={titleFontStyle}
|
|
||||||
>
|
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Info spans */}
|
{/* Info spans */}
|
||||||
{infoSpans.length > 0 && (
|
{infoSpans.length > 0 && (
|
||||||
<div
|
<div style={spanGridStyle}>
|
||||||
className='grid gap-2'
|
{infoSpans.map((span) => {
|
||||||
style={{ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }}
|
// For icon spans, remove padding so image fills container
|
||||||
>
|
const spanItemStyle = span.iconUrl
|
||||||
{infoSpans.map((span) => (
|
? { ...spanStyle, padding: 0, overflow: 'hidden' }
|
||||||
<div
|
: spanStyle;
|
||||||
key={span.id}
|
|
||||||
className='bg-slate-700 text-amber-50 text-center py-2 px-2 rounded-lg text-xs font-medium'
|
return (
|
||||||
style={textFontStyle}
|
<div
|
||||||
>
|
key={span.id}
|
||||||
{span.text}
|
className='text-center flex items-center justify-center'
|
||||||
</div>
|
style={spanItemStyle}
|
||||||
))}
|
>
|
||||||
|
{span.iconUrl ? (
|
||||||
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
|
<img
|
||||||
|
src={resolve(span.iconUrl)}
|
||||||
|
alt=''
|
||||||
|
className='w-full h-full object-cover'
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
span.text
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Gallery cards */}
|
{/* Gallery cards */}
|
||||||
{cards.length > 0 && (
|
{cards.length > 0 && (
|
||||||
<div
|
<div style={cardGridStyle}>
|
||||||
className='grid gap-2 w-full'
|
|
||||||
style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}
|
|
||||||
>
|
|
||||||
{cards.map((card, index) => (
|
{cards.map((card, index) => (
|
||||||
<div
|
<div
|
||||||
key={card.id}
|
key={card.id}
|
||||||
className={`relative aspect-[4/3] min-w-[50px] min-h-[40px] ${
|
className={`relative aspect-[4/3] min-w-[50px] min-h-[40px] overflow-hidden ${
|
||||||
onCardClick
|
onCardClick
|
||||||
? 'cursor-pointer hover:ring-2 hover:ring-white hover:ring-offset-1 hover:ring-offset-black/50 transition-all'
|
? 'cursor-pointer hover:ring-2 hover:ring-white hover:ring-offset-1 hover:ring-offset-black/50 transition-all'
|
||||||
: ''
|
: ''
|
||||||
}`}
|
}`}
|
||||||
|
style={cardStyle}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (onCardClick) {
|
if (onCardClick) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -134,18 +175,14 @@ const GalleryElement: React.FC<GalleryElementProps> = ({
|
|||||||
<img
|
<img
|
||||||
src={resolve(card.imageUrl)}
|
src={resolve(card.imageUrl)}
|
||||||
alt={card.title || ''}
|
alt={card.title || ''}
|
||||||
className='absolute inset-0 w-full h-full object-cover rounded-lg'
|
className='absolute inset-0 w-full h-full object-cover'
|
||||||
|
style={{ borderRadius: cardStyle.borderRadius }}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{card.title && (
|
{card.title && (
|
||||||
<div className='absolute inset-0 flex items-center justify-center'>
|
<div className='absolute inset-0 flex items-center justify-center'>
|
||||||
<span
|
<span style={cardTitleStyle}>{card.title}</span>
|
||||||
className='text-white text-xs font-bold drop-shadow-lg'
|
|
||||||
style={textFontStyle}
|
|
||||||
>
|
|
||||||
{card.title}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -63,13 +63,16 @@ const TooltipElement: React.FC<TooltipElementProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Without icon: render text tooltip
|
// Without icon: render text tooltip
|
||||||
|
// Inheritable styles (color, fontSize, fontWeight) cascade from General Element Styles
|
||||||
|
// Font family can be set per-section via tooltipTitleFontFamily/tooltipTextFontFamily
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} style={style}>
|
<div className={className} style={style}>
|
||||||
<div className='p-3 max-w-[200px]'>
|
<div className='p-3 max-w-[200px]'>
|
||||||
<p className='font-bold text-sm' style={titleFontStyle}>
|
<p style={titleFontStyle}>
|
||||||
{element.tooltipTitle}
|
{element.tooltipTitle}
|
||||||
</p>
|
</p>
|
||||||
<p className='text-xs opacity-70' style={textFontStyle}>
|
<p style={{ opacity: 0.7, ...textFontStyle }}>
|
||||||
{element.tooltipText}
|
{element.tooltipText}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -87,7 +87,7 @@ interface UseConstructorElementsResult {
|
|||||||
/** Gallery info span operations */
|
/** Gallery info span operations */
|
||||||
galleryInfoSpans: {
|
galleryInfoSpans: {
|
||||||
add: () => void;
|
add: () => void;
|
||||||
update: (spanId: string, text: string) => void;
|
update: (spanId: string, patch: Partial<GalleryInfoSpan>) => void;
|
||||||
remove: (spanId: string) => void;
|
remove: (spanId: string) => void;
|
||||||
};
|
};
|
||||||
/** Carousel slide operations */
|
/** Carousel slide operations */
|
||||||
@ -360,11 +360,11 @@ export function useConstructorElements({
|
|||||||
];
|
];
|
||||||
updateSelectedElement({ galleryInfoSpans: nextSpans });
|
updateSelectedElement({ galleryInfoSpans: nextSpans });
|
||||||
},
|
},
|
||||||
update: (spanId: string, text: string) => {
|
update: (spanId: string, patch: Partial<GalleryInfoSpan>) => {
|
||||||
if (!selectedElement || !isGalleryElementType(selectedElement.type))
|
if (!selectedElement || !isGalleryElementType(selectedElement.type))
|
||||||
return;
|
return;
|
||||||
const nextSpans = (selectedElement.galleryInfoSpans || []).map((span) =>
|
const nextSpans = (selectedElement.galleryInfoSpans || []).map((span) =>
|
||||||
span.id === spanId ? { ...span, text } : span,
|
span.id === spanId ? { ...span, ...patch } : span,
|
||||||
);
|
);
|
||||||
updateSelectedElement({ galleryInfoSpans: nextSpans });
|
updateSelectedElement({ galleryInfoSpans: nextSpans });
|
||||||
},
|
},
|
||||||
|
|||||||
@ -9,10 +9,12 @@ import type {
|
|||||||
CanvasElement,
|
CanvasElement,
|
||||||
CanvasElementType,
|
CanvasElementType,
|
||||||
GalleryCard,
|
GalleryCard,
|
||||||
|
GalleryInfoSpan,
|
||||||
CarouselSlide,
|
CarouselSlide,
|
||||||
NavigationButtonKind,
|
NavigationButtonKind,
|
||||||
} from '../types/constructor';
|
} from '../types/constructor';
|
||||||
import { ELEMENT_STYLE_PROPS } from './elementStyles';
|
import { ELEMENT_STYLE_PROPS } from './elementStyles';
|
||||||
|
import { GALLERY_SECTION_STYLE_PROPS } from './gallerySectionStyles';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a local unique ID for elements
|
* Generate a local unique ID for elements
|
||||||
@ -230,6 +232,17 @@ export const normalizeGalleryCard = (
|
|||||||
description: String(card?.description || ''),
|
description: String(card?.description || ''),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a gallery info span from unknown input
|
||||||
|
*/
|
||||||
|
export const normalizeGalleryInfoSpan = (
|
||||||
|
span: Record<string, unknown>,
|
||||||
|
): GalleryInfoSpan => ({
|
||||||
|
id: String(span?.id || createLocalId()),
|
||||||
|
text: String(span?.text || ''),
|
||||||
|
iconUrl: span?.iconUrl ? String(span.iconUrl) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize a carousel slide from unknown input
|
* Normalize a carousel slide from unknown input
|
||||||
*/
|
*/
|
||||||
@ -313,6 +326,18 @@ export const mergeElementWithDefaults = (
|
|||||||
merged.galleryCards = cards.map((card, i) =>
|
merged.galleryCards = cards.map((card, i) =>
|
||||||
normalizeGalleryCard(card as unknown as Record<string, unknown>, i),
|
normalizeGalleryCard(card as unknown as Record<string, unknown>, i),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle gallery info spans array
|
||||||
|
const spans = preferElementValues
|
||||||
|
? Array.isArray(element.galleryInfoSpans)
|
||||||
|
? element.galleryInfoSpans
|
||||||
|
: defaults.galleryInfoSpans || []
|
||||||
|
: Array.isArray(defaults.galleryInfoSpans)
|
||||||
|
? defaults.galleryInfoSpans
|
||||||
|
: element.galleryInfoSpans || [];
|
||||||
|
merged.galleryInfoSpans = spans.map((span) =>
|
||||||
|
normalizeGalleryInfoSpan(span as unknown as Record<string, unknown>),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle carousel slides array
|
// Handle carousel slides array
|
||||||
@ -360,6 +385,13 @@ export const parseElementSettings = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse gallery info spans if present
|
||||||
|
if (Array.isArray(settings.galleryInfoSpans)) {
|
||||||
|
settings.galleryInfoSpans = settings.galleryInfoSpans.map((span) =>
|
||||||
|
normalizeGalleryInfoSpan(span as Record<string, unknown>),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse carousel slides if present
|
// Parse carousel slides if present
|
||||||
if (Array.isArray(settings.carouselSlides)) {
|
if (Array.isArray(settings.carouselSlides)) {
|
||||||
settings.carouselSlides = settings.carouselSlides.map((slide, i) =>
|
settings.carouselSlides = settings.carouselSlides.map((slide, i) =>
|
||||||
@ -525,6 +557,13 @@ export const buildElementSettings = (
|
|||||||
description: card.description || '',
|
description: card.description || '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(element.galleryInfoSpans)) {
|
||||||
|
settings.galleryInfoSpans = element.galleryInfoSpans.map((span) => ({
|
||||||
|
id: String(span.id || createLocalId()),
|
||||||
|
text: span.text || '',
|
||||||
|
iconUrl: span.iconUrl || undefined,
|
||||||
|
}));
|
||||||
|
}
|
||||||
addIfNotEmpty(
|
addIfNotEmpty(
|
||||||
settings,
|
settings,
|
||||||
'galleryTitleFontFamily',
|
'galleryTitleFontFamily',
|
||||||
@ -535,6 +574,19 @@ export const buildElementSettings = (
|
|||||||
'galleryTextFontFamily',
|
'galleryTextFontFamily',
|
||||||
element.galleryTextFontFamily,
|
element.galleryTextFontFamily,
|
||||||
);
|
);
|
||||||
|
addIfNotEmpty(settings, 'galleryColumns', element.galleryColumns);
|
||||||
|
addIfNotEmpty(
|
||||||
|
settings,
|
||||||
|
'galleryHeaderImageUrl',
|
||||||
|
element.galleryHeaderImageUrl,
|
||||||
|
);
|
||||||
|
addIfNotEmpty(settings, 'galleryHeaderText', element.galleryHeaderText);
|
||||||
|
addIfNotEmpty(settings, 'galleryTitle', element.galleryTitle);
|
||||||
|
// Gallery section style properties
|
||||||
|
GALLERY_SECTION_STYLE_PROPS.forEach((prop) => {
|
||||||
|
const value = (element as Record<string, unknown>)[prop];
|
||||||
|
addIfNotEmpty(settings, prop, value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carousel type settings
|
// Carousel type settings
|
||||||
|
|||||||
343
frontend/src/lib/gallerySectionStyles.ts
Normal file
343
frontend/src/lib/gallerySectionStyles.ts
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
/**
|
||||||
|
* Gallery Section Styles
|
||||||
|
*
|
||||||
|
* Unified types and utilities for gallery element section styling.
|
||||||
|
* Follows the same pattern as elementStyles.ts for consistency.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
|
import type { CanvasElement } from '../types/constructor';
|
||||||
|
import { getFontByKey, getFontStyle } from './fonts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gallery section names for styling
|
||||||
|
*/
|
||||||
|
export type GallerySectionName = 'header' | 'title' | 'span' | 'card' | 'wrapper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for gallery sections to preserve current Tailwind appearance
|
||||||
|
*/
|
||||||
|
export const GALLERY_SECTION_DEFAULTS: Record<GallerySectionName, CSSProperties> = {
|
||||||
|
header: {
|
||||||
|
fontSize: '1.5rem', // text-2xl
|
||||||
|
fontWeight: '700', // font-bold
|
||||||
|
padding: '0.25rem 0.5rem', // px-1 py-2
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
backgroundColor: '#fefce8', // bg-amber-50
|
||||||
|
color: '#1e293b', // text-slate-800
|
||||||
|
fontSize: '0.875rem', // text-sm
|
||||||
|
fontWeight: '600', // font-semibold
|
||||||
|
padding: '0.5rem 0.75rem', // py-2 px-3
|
||||||
|
borderRadius: '0.5rem', // rounded-lg
|
||||||
|
},
|
||||||
|
span: {
|
||||||
|
backgroundColor: '#334155', // bg-slate-700
|
||||||
|
color: '#fef3c7', // text-amber-50
|
||||||
|
fontSize: '0.75rem', // text-xs
|
||||||
|
fontWeight: '500', // font-medium
|
||||||
|
padding: '0.5rem', // py-2 px-2
|
||||||
|
borderRadius: '0.5rem', // rounded-lg
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
borderRadius: '0.5rem', // rounded-lg
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.6)', // bg-black/60
|
||||||
|
padding: '0.75rem', // p-3
|
||||||
|
borderRadius: '0.75rem', // rounded-xl
|
||||||
|
gap: '0.5rem', // gap-2
|
||||||
|
backdropFilter: 'blur(4px)', // backdrop-blur-sm
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get trimmed value from unknown input
|
||||||
|
*/
|
||||||
|
const getTrimmedValue = (value: unknown): string => {
|
||||||
|
if (value === null || value === undefined) return '';
|
||||||
|
if (value === 0) return '0';
|
||||||
|
return String(value).trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply value with default fallback
|
||||||
|
*/
|
||||||
|
const applyWithDefault = (
|
||||||
|
style: CSSProperties,
|
||||||
|
prop: keyof CSSProperties,
|
||||||
|
value: unknown,
|
||||||
|
defaultValue: unknown,
|
||||||
|
): void => {
|
||||||
|
const trimmed = getTrimmedValue(value);
|
||||||
|
if (trimmed) {
|
||||||
|
(style as Record<string, unknown>)[prop] = trimmed;
|
||||||
|
} else if (defaultValue !== undefined) {
|
||||||
|
(style as Record<string, unknown>)[prop] = defaultValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply value only if explicitly set (no default - allows CSS inheritance)
|
||||||
|
*/
|
||||||
|
const applyIfSet = (
|
||||||
|
style: CSSProperties,
|
||||||
|
prop: keyof CSSProperties,
|
||||||
|
value: unknown,
|
||||||
|
): void => {
|
||||||
|
const trimmed = getTrimmedValue(value);
|
||||||
|
if (trimmed) {
|
||||||
|
(style as Record<string, unknown>)[prop] = trimmed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build CSS style object for gallery header section
|
||||||
|
*/
|
||||||
|
export function buildGalleryHeaderStyle(
|
||||||
|
element: Partial<CanvasElement>,
|
||||||
|
): CSSProperties {
|
||||||
|
const defaults = GALLERY_SECTION_DEFAULTS.header;
|
||||||
|
const style: CSSProperties = {};
|
||||||
|
|
||||||
|
applyIfSet(style, 'backgroundColor', element.galleryHeaderBackgroundColor);
|
||||||
|
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||||
|
applyIfSet(style, 'color', element.galleryHeaderColor);
|
||||||
|
applyIfSet(style, 'fontSize', element.galleryHeaderFontSize);
|
||||||
|
applyIfSet(style, 'fontWeight', element.galleryHeaderFontWeight);
|
||||||
|
// Non-inheritable properties: use defaults
|
||||||
|
applyWithDefault(style, 'padding', element.galleryHeaderPadding, defaults.padding);
|
||||||
|
applyIfSet(style, 'borderRadius', element.galleryHeaderBorderRadius);
|
||||||
|
applyIfSet(style, 'border', element.galleryHeaderBorder);
|
||||||
|
|
||||||
|
// Apply font family with font library resolution
|
||||||
|
const fontKey = element.galleryHeaderFontFamily;
|
||||||
|
if (fontKey) {
|
||||||
|
const font = getFontByKey(fontKey);
|
||||||
|
if (font) {
|
||||||
|
Object.assign(style, getFontStyle(font));
|
||||||
|
} else {
|
||||||
|
style.fontFamily = fontKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build CSS style object for gallery title section
|
||||||
|
*/
|
||||||
|
export function buildGalleryTitleStyle(
|
||||||
|
element: Partial<CanvasElement>,
|
||||||
|
): CSSProperties {
|
||||||
|
const defaults = GALLERY_SECTION_DEFAULTS.title;
|
||||||
|
const style: CSSProperties = {};
|
||||||
|
|
||||||
|
applyWithDefault(style, 'backgroundColor', element.galleryTitleBackgroundColor, defaults.backgroundColor);
|
||||||
|
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||||
|
applyIfSet(style, 'color', element.galleryTitleColor);
|
||||||
|
applyIfSet(style, 'fontSize', element.galleryTitleFontSize);
|
||||||
|
applyIfSet(style, 'fontWeight', element.galleryTitleFontWeight);
|
||||||
|
// Non-inheritable properties: use defaults
|
||||||
|
applyWithDefault(style, 'padding', element.galleryTitlePadding, defaults.padding);
|
||||||
|
applyWithDefault(style, 'borderRadius', element.galleryTitleBorderRadius, defaults.borderRadius);
|
||||||
|
applyIfSet(style, 'border', element.galleryTitleBorder);
|
||||||
|
|
||||||
|
// Apply font family with font library resolution
|
||||||
|
const fontKey = element.galleryTitleFontFamily;
|
||||||
|
if (fontKey) {
|
||||||
|
const font = getFontByKey(fontKey);
|
||||||
|
if (font) {
|
||||||
|
Object.assign(style, getFontStyle(font));
|
||||||
|
} else {
|
||||||
|
style.fontFamily = fontKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build CSS style object for gallery span items
|
||||||
|
*/
|
||||||
|
export function buildGallerySpanStyle(
|
||||||
|
element: Partial<CanvasElement>,
|
||||||
|
): CSSProperties {
|
||||||
|
const defaults = GALLERY_SECTION_DEFAULTS.span;
|
||||||
|
const style: CSSProperties = {};
|
||||||
|
|
||||||
|
applyWithDefault(style, 'backgroundColor', element.gallerySpanBackgroundColor, defaults.backgroundColor);
|
||||||
|
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||||
|
applyIfSet(style, 'color', element.gallerySpanColor);
|
||||||
|
applyIfSet(style, 'fontSize', element.gallerySpanFontSize);
|
||||||
|
applyIfSet(style, 'fontWeight', element.gallerySpanFontWeight);
|
||||||
|
// Non-inheritable properties: use defaults
|
||||||
|
applyWithDefault(style, 'padding', element.gallerySpanPadding, defaults.padding);
|
||||||
|
applyWithDefault(style, 'borderRadius', element.gallerySpanBorderRadius, defaults.borderRadius);
|
||||||
|
applyIfSet(style, 'border', element.gallerySpanBorder);
|
||||||
|
|
||||||
|
// Apply font family with font library resolution (fallback to galleryTextFontFamily for legacy support)
|
||||||
|
const fontKey = element.gallerySpanFontFamily || element.galleryTextFontFamily;
|
||||||
|
if (fontKey) {
|
||||||
|
const font = getFontByKey(fontKey);
|
||||||
|
if (font) {
|
||||||
|
Object.assign(style, getFontStyle(font));
|
||||||
|
} else {
|
||||||
|
style.fontFamily = fontKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build CSS style object for gallery span grid container
|
||||||
|
*/
|
||||||
|
export function buildGallerySpanGridStyle(
|
||||||
|
element: Partial<CanvasElement>,
|
||||||
|
): CSSProperties {
|
||||||
|
const columns = getGalleryGridColumns(element, 'span');
|
||||||
|
const gap = getTrimmedValue(element.gallerySpanGap) || '0.5rem';
|
||||||
|
|
||||||
|
return {
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
||||||
|
gap,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build CSS style object for gallery card items
|
||||||
|
*/
|
||||||
|
export function buildGalleryCardStyle(
|
||||||
|
element: Partial<CanvasElement>,
|
||||||
|
): CSSProperties {
|
||||||
|
const defaults = GALLERY_SECTION_DEFAULTS.card;
|
||||||
|
const style: CSSProperties = {};
|
||||||
|
|
||||||
|
applyWithDefault(style, 'backgroundColor', element.galleryCardBackgroundColor, undefined);
|
||||||
|
applyWithDefault(style, 'borderRadius', element.galleryCardBorderRadius, defaults.borderRadius);
|
||||||
|
applyWithDefault(style, 'border', element.galleryCardBorder, undefined);
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build CSS style object for gallery card title overlay
|
||||||
|
*/
|
||||||
|
export function buildGalleryCardTitleStyle(
|
||||||
|
element: Partial<CanvasElement>,
|
||||||
|
): CSSProperties {
|
||||||
|
const style: CSSProperties = {};
|
||||||
|
|
||||||
|
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||||
|
// Note: card titles typically need white text for visibility over images
|
||||||
|
applyIfSet(style, 'color', element.galleryCardTitleColor);
|
||||||
|
applyIfSet(style, 'fontSize', element.galleryCardTitleFontSize);
|
||||||
|
applyIfSet(style, 'fontWeight', element.galleryCardTitleFontWeight);
|
||||||
|
|
||||||
|
if (element.galleryCardTitleBackgroundColor) {
|
||||||
|
style.backgroundColor = element.galleryCardTitleBackgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle text shadow
|
||||||
|
const shadow = element.galleryCardTitleShadow;
|
||||||
|
if (shadow) {
|
||||||
|
style.textShadow = shadow;
|
||||||
|
} else {
|
||||||
|
// Default drop-shadow-lg equivalent for visibility over images
|
||||||
|
style.filter = 'drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply font family from galleryTextFontFamily for legacy support
|
||||||
|
const fontKey = element.galleryTextFontFamily;
|
||||||
|
if (fontKey) {
|
||||||
|
const font = getFontByKey(fontKey);
|
||||||
|
if (font) {
|
||||||
|
Object.assign(style, getFontStyle(font));
|
||||||
|
} else {
|
||||||
|
style.fontFamily = fontKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build CSS style object for gallery cards grid container
|
||||||
|
*/
|
||||||
|
export function buildGalleryCardGridStyle(
|
||||||
|
element: Partial<CanvasElement>,
|
||||||
|
): CSSProperties {
|
||||||
|
const columns = getGalleryGridColumns(element, 'card');
|
||||||
|
const gap = getTrimmedValue(element.galleryCardGap) || '0.5rem';
|
||||||
|
|
||||||
|
return {
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
||||||
|
gap,
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get grid columns with fallback to legacy galleryColumns
|
||||||
|
*/
|
||||||
|
export function getGalleryGridColumns(
|
||||||
|
element: Partial<CanvasElement>,
|
||||||
|
section: 'span' | 'card',
|
||||||
|
): number {
|
||||||
|
if (section === 'span') {
|
||||||
|
return element.gallerySpanColumns ?? element.galleryColumns ?? 3;
|
||||||
|
}
|
||||||
|
// For cards, use galleryCardColumns with fallback to legacy galleryColumns
|
||||||
|
return element.galleryCardColumns ?? element.galleryColumns ?? 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All gallery section style property names for iteration
|
||||||
|
*/
|
||||||
|
export const GALLERY_SECTION_STYLE_PROPS = [
|
||||||
|
// Header
|
||||||
|
'galleryHeaderBackgroundColor',
|
||||||
|
'galleryHeaderColor',
|
||||||
|
'galleryHeaderFontFamily',
|
||||||
|
'galleryHeaderFontSize',
|
||||||
|
'galleryHeaderFontWeight',
|
||||||
|
'galleryHeaderPadding',
|
||||||
|
'galleryHeaderBorderRadius',
|
||||||
|
'galleryHeaderBorder',
|
||||||
|
// Title
|
||||||
|
'galleryTitleBackgroundColor',
|
||||||
|
'galleryTitleColor',
|
||||||
|
'galleryTitleFontFamily',
|
||||||
|
'galleryTitleFontSize',
|
||||||
|
'galleryTitleFontWeight',
|
||||||
|
'galleryTitlePadding',
|
||||||
|
'galleryTitleBorderRadius',
|
||||||
|
'galleryTitleBorder',
|
||||||
|
// Spans
|
||||||
|
'gallerySpanBackgroundColor',
|
||||||
|
'gallerySpanColor',
|
||||||
|
'gallerySpanFontFamily',
|
||||||
|
'gallerySpanFontSize',
|
||||||
|
'gallerySpanFontWeight',
|
||||||
|
'gallerySpanPadding',
|
||||||
|
'gallerySpanBorderRadius',
|
||||||
|
'gallerySpanBorder',
|
||||||
|
'gallerySpanGap',
|
||||||
|
'gallerySpanColumns',
|
||||||
|
// Cards
|
||||||
|
'galleryCardBackgroundColor',
|
||||||
|
'galleryCardBorderRadius',
|
||||||
|
'galleryCardBorder',
|
||||||
|
'galleryCardGap',
|
||||||
|
'galleryCardColumns',
|
||||||
|
'galleryCardTitleColor',
|
||||||
|
'galleryCardTitleBackgroundColor',
|
||||||
|
'galleryCardTitleFontSize',
|
||||||
|
'galleryCardTitleFontWeight',
|
||||||
|
'galleryCardTitleShadow',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type GallerySectionStyleProp = (typeof GALLERY_SECTION_STYLE_PROPS)[number];
|
||||||
@ -47,6 +47,7 @@ export interface GalleryCard {
|
|||||||
export interface GalleryInfoSpan {
|
export interface GalleryInfoSpan {
|
||||||
id: string;
|
id: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
iconUrl?: string; // Renders icon instead of text when set
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,6 +101,52 @@ export interface CanvasElement extends BaseCanvasElement {
|
|||||||
galleryColumns?: number;
|
galleryColumns?: number;
|
||||||
galleryTitleFontFamily?: string;
|
galleryTitleFontFamily?: string;
|
||||||
galleryTextFontFamily?: string;
|
galleryTextFontFamily?: string;
|
||||||
|
// Gallery Section Styles - Header
|
||||||
|
galleryHeaderBackgroundColor?: string;
|
||||||
|
galleryHeaderColor?: string;
|
||||||
|
galleryHeaderFontFamily?: string;
|
||||||
|
galleryHeaderFontSize?: string;
|
||||||
|
galleryHeaderFontWeight?: string;
|
||||||
|
galleryHeaderPadding?: string;
|
||||||
|
galleryHeaderBorderRadius?: string;
|
||||||
|
galleryHeaderBorder?: string;
|
||||||
|
// Gallery Section Styles - Title
|
||||||
|
galleryTitleBackgroundColor?: string;
|
||||||
|
galleryTitleColor?: string;
|
||||||
|
galleryTitleFontSize?: string;
|
||||||
|
galleryTitleFontWeight?: string;
|
||||||
|
galleryTitlePadding?: string;
|
||||||
|
galleryTitleBorderRadius?: string;
|
||||||
|
galleryTitleBorder?: string;
|
||||||
|
// Gallery Section Styles - Spans
|
||||||
|
gallerySpanBackgroundColor?: string;
|
||||||
|
gallerySpanColor?: string;
|
||||||
|
gallerySpanFontFamily?: string;
|
||||||
|
gallerySpanFontSize?: string;
|
||||||
|
gallerySpanFontWeight?: string;
|
||||||
|
gallerySpanPadding?: string;
|
||||||
|
gallerySpanBorderRadius?: string;
|
||||||
|
gallerySpanBorder?: string;
|
||||||
|
gallerySpanGap?: string;
|
||||||
|
gallerySpanColumns?: number;
|
||||||
|
// Gallery Section Styles - Cards
|
||||||
|
galleryCardBackgroundColor?: string;
|
||||||
|
galleryCardBorderRadius?: string;
|
||||||
|
galleryCardBorder?: string;
|
||||||
|
galleryCardGap?: string;
|
||||||
|
galleryCardColumns?: number;
|
||||||
|
galleryCardTitleColor?: string;
|
||||||
|
galleryCardTitleBackgroundColor?: string;
|
||||||
|
galleryCardTitleFontSize?: string;
|
||||||
|
galleryCardTitleFontWeight?: string;
|
||||||
|
galleryCardTitleShadow?: string;
|
||||||
|
// Gallery Section Styles - Wrapper
|
||||||
|
galleryWrapperBackgroundColor?: string;
|
||||||
|
galleryWrapperPadding?: string;
|
||||||
|
galleryWrapperBorderRadius?: string;
|
||||||
|
galleryWrapperBorder?: string;
|
||||||
|
galleryWrapperGap?: string;
|
||||||
|
galleryWrapperBackdropBlur?: string;
|
||||||
carouselSlides?: CarouselSlide[];
|
carouselSlides?: CarouselSlide[];
|
||||||
carouselCaptionFontFamily?: string;
|
carouselCaptionFontFamily?: string;
|
||||||
carouselPrevIconUrl?: string;
|
carouselPrevIconUrl?: string;
|
||||||
@ -401,7 +448,7 @@ export interface EditorCollectionOpsProps {
|
|||||||
};
|
};
|
||||||
galleryInfoSpans: {
|
galleryInfoSpans: {
|
||||||
add: () => void;
|
add: () => void;
|
||||||
update: (spanId: string, text: string) => void;
|
update: (spanId: string, patch: Partial<GalleryInfoSpan>) => void;
|
||||||
remove: (spanId: string) => void;
|
remove: (spanId: string) => void;
|
||||||
};
|
};
|
||||||
carouselSlides: {
|
carouselSlides: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user