improved menus in constructor
This commit is contained in:
parent
4634ad9207
commit
f06a2b2c97
@ -58,7 +58,7 @@ const BackgroundSettingsEditor: React.FC<BackgroundSettingsEditorProps> = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
{label}
|
||||
</label>
|
||||
<select
|
||||
@ -81,17 +81,17 @@ const BackgroundSettingsEditor: React.FC<BackgroundSettingsEditorProps> = ({
|
||||
))}
|
||||
</select>
|
||||
{durationNote && (
|
||||
<p className='mt-1 text-[11px] text-gray-500'>{durationNote}</p>
|
||||
<p className='mt-1 text-[11px] text-white/60'>{durationNote}</p>
|
||||
)}
|
||||
|
||||
{/* Video Playback Settings */}
|
||||
{showVideoSettings && (
|
||||
<div className='mt-3 space-y-2 border-t border-gray-200 pt-3'>
|
||||
<p className='text-[10px] font-semibold uppercase text-gray-500'>
|
||||
<div className='mt-3 space-y-2 border-t border-white/20 pt-3'>
|
||||
<p className='text-[10px] font-semibold uppercase text-white/70'>
|
||||
Playback Settings
|
||||
</p>
|
||||
|
||||
<label className='flex cursor-pointer items-center gap-2 text-[11px] text-gray-700'>
|
||||
<label className='flex cursor-pointer items-center gap-2 text-[11px] text-white/80'>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='h-3 w-3 rounded border-gray-300'
|
||||
@ -103,7 +103,7 @@ const BackgroundSettingsEditor: React.FC<BackgroundSettingsEditorProps> = ({
|
||||
Autoplay
|
||||
</label>
|
||||
|
||||
<label className='flex cursor-pointer items-center gap-2 text-[11px] text-gray-700'>
|
||||
<label className='flex cursor-pointer items-center gap-2 text-[11px] text-white/80'>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='h-3 w-3 rounded border-gray-300'
|
||||
@ -115,7 +115,7 @@ const BackgroundSettingsEditor: React.FC<BackgroundSettingsEditorProps> = ({
|
||||
Loop
|
||||
</label>
|
||||
|
||||
<label className='flex cursor-pointer items-center gap-2 text-[11px] text-gray-700'>
|
||||
<label className='flex cursor-pointer items-center gap-2 text-[11px] text-white/80'>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='h-3 w-3 rounded border-gray-300'
|
||||
@ -129,7 +129,7 @@ const BackgroundSettingsEditor: React.FC<BackgroundSettingsEditorProps> = ({
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='flex-1'>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Start (sec)
|
||||
</label>
|
||||
<input
|
||||
@ -149,7 +149,7 @@ const BackgroundSettingsEditor: React.FC<BackgroundSettingsEditorProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
End (sec)
|
||||
</label>
|
||||
<input
|
||||
|
||||
@ -1,77 +0,0 @@
|
||||
/**
|
||||
* ConstructorControlsPanel Component
|
||||
*
|
||||
* Draggable panel with page selector, mode toggle, and exit button.
|
||||
* Used in constructor for top-level navigation controls.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import BaseButton from '../BaseButton';
|
||||
import { mdiExitToApp } from '@mdi/js';
|
||||
import PageSelector from './PageSelector';
|
||||
import InteractionModeToggle from './InteractionModeToggle';
|
||||
import type { Position, TourPage, ConstructorInteractionMode } from './types';
|
||||
|
||||
interface ConstructorControlsPanelProps {
|
||||
projectId: string;
|
||||
pages: TourPage[];
|
||||
activePageId: string;
|
||||
interactionMode: ConstructorInteractionMode;
|
||||
position: Position;
|
||||
onPageChange: (pageId: string) => void;
|
||||
onModeChange: (mode: ConstructorInteractionMode) => void;
|
||||
onDragStart: (event: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
const ConstructorControlsPanel: React.FC<ConstructorControlsPanelProps> = ({
|
||||
projectId,
|
||||
pages,
|
||||
activePageId,
|
||||
interactionMode,
|
||||
position,
|
||||
onPageChange,
|
||||
onModeChange,
|
||||
onDragStart,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className='fixed z-[1000] w-[min(92vw,460px)] rounded-lg border border-gray-200 bg-white shadow-xl'
|
||||
style={{
|
||||
left: position.x,
|
||||
top: position.y,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='flex cursor-move items-center justify-between rounded-t-lg border-b border-gray-200 bg-gray-50 px-3 py-2'
|
||||
onMouseDown={onDragStart}
|
||||
>
|
||||
<span className='text-xs font-bold uppercase'>
|
||||
Constructor Controls
|
||||
</span>
|
||||
</div>
|
||||
<div className='space-y-2 p-3'>
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
<PageSelector
|
||||
pages={pages}
|
||||
activePageId={activePageId}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
<BaseButton
|
||||
color='lightDark'
|
||||
label='Exit to Assets'
|
||||
icon={mdiExitToApp}
|
||||
href={
|
||||
projectId ? `/projects/${projectId}` : '/projects/projects-list'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<InteractionModeToggle
|
||||
mode={interactionMode}
|
||||
onModeChange={onModeChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConstructorControlsPanel;
|
||||
@ -1,195 +0,0 @@
|
||||
/**
|
||||
* ConstructorMenu Component
|
||||
*
|
||||
* Draggable menu panel with actions for adding elements, backgrounds, etc.
|
||||
*/
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import BaseButton from '../BaseButton';
|
||||
import {
|
||||
mdiMenu,
|
||||
mdiImageMultiple,
|
||||
mdiViewCarousel,
|
||||
mdiTooltipText,
|
||||
mdiSwapHorizontal,
|
||||
mdiText,
|
||||
mdiPlus,
|
||||
mdiExitToApp,
|
||||
} from '@mdi/js';
|
||||
import MenuActionButton from './MenuActionButton';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import type {
|
||||
Position,
|
||||
CanvasElementType,
|
||||
NavigationElementType,
|
||||
} from './types';
|
||||
import type { EditorMenuItem } from '../../types/constructor';
|
||||
|
||||
interface ConstructorMenuProps {
|
||||
position: Position;
|
||||
isOpen: boolean;
|
||||
allowedNavigationTypes: NavigationElementType[];
|
||||
isCreatingPage: boolean;
|
||||
isSaving: boolean;
|
||||
isSavingToStage: boolean;
|
||||
onDragStart: (event: React.MouseEvent) => void;
|
||||
onToggleOpen: () => void;
|
||||
onSelectMenuItem: (item: EditorMenuItem) => void;
|
||||
onAddElement: (type: CanvasElementType) => void;
|
||||
onCreatePage: () => void;
|
||||
onSave: () => void;
|
||||
onSaveToStage: () => void;
|
||||
onExit: () => void;
|
||||
/** Page's last saved timestamp (updatedAt from tour_pages) */
|
||||
lastSavedAt?: string | null;
|
||||
/** Last save-to-stage timestamp */
|
||||
lastSavedToStageAt?: string | null;
|
||||
}
|
||||
|
||||
const ConstructorMenu = forwardRef<HTMLDivElement, ConstructorMenuProps>(
|
||||
(
|
||||
{
|
||||
position,
|
||||
isOpen,
|
||||
allowedNavigationTypes,
|
||||
isCreatingPage,
|
||||
isSaving,
|
||||
isSavingToStage,
|
||||
onDragStart,
|
||||
onToggleOpen,
|
||||
onSelectMenuItem,
|
||||
onAddElement,
|
||||
onCreatePage,
|
||||
onSave,
|
||||
onSaveToStage,
|
||||
onExit,
|
||||
lastSavedAt,
|
||||
lastSavedToStageAt,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className='fixed z-[1000] w-60 border border-gray-200 rounded-lg bg-white shadow-xl'
|
||||
style={{ left: position.x, top: position.y }}
|
||||
>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 py-2 border-b border-gray-200 cursor-move bg-gray-50 rounded-t-lg'
|
||||
onMouseDown={onDragStart}
|
||||
>
|
||||
<span className='text-xs font-bold uppercase'>Constructor Menu</span>
|
||||
<button type='button' onClick={onToggleOpen}>
|
||||
<BaseIcon path={mdiMenu} size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div className='p-2 space-y-1 max-h-[calc(100vh-120px)] overflow-y-auto'>
|
||||
<MenuActionButton
|
||||
icon={mdiImageMultiple}
|
||||
label='Background Image'
|
||||
onClick={() => onSelectMenuItem('background_image')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiViewCarousel}
|
||||
label='Background Video'
|
||||
onClick={() => onSelectMenuItem('background_video')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiTooltipText}
|
||||
label='Background Audio'
|
||||
onClick={() => onSelectMenuItem('background_audio')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiSwapHorizontal}
|
||||
label='Add Navigation Button'
|
||||
onClick={() => onAddElement(allowedNavigationTypes[0])}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiSwapHorizontal}
|
||||
label='Add Transition'
|
||||
onClick={() => onSelectMenuItem('create_transition')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiImageMultiple}
|
||||
label='Add Gallery'
|
||||
onClick={() => onAddElement('gallery')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiViewCarousel}
|
||||
label='Add Carousel'
|
||||
onClick={() => onAddElement('carousel')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiTooltipText}
|
||||
label='Add Tooltip'
|
||||
onClick={() => onAddElement('tooltip')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiText}
|
||||
label='Add Description'
|
||||
onClick={() => onAddElement('description')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiViewCarousel}
|
||||
label='Add Video Player'
|
||||
onClick={() => onAddElement('video_player')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiTooltipText}
|
||||
label='Add Audio Player'
|
||||
onClick={() => onAddElement('audio_player')}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiPlus}
|
||||
label={isCreatingPage ? 'Creating Page...' : 'Create New Page'}
|
||||
onClick={onCreatePage}
|
||||
disabled={isCreatingPage}
|
||||
/>
|
||||
|
||||
<div className='pt-2 border-t border-gray-200 space-y-1'>
|
||||
<BaseButton
|
||||
small
|
||||
color='info'
|
||||
label={isSaving ? 'Saving...' : 'Save'}
|
||||
subtitle={
|
||||
lastSavedAt
|
||||
? dataFormatter.relativeTimestamp(lastSavedAt)
|
||||
: undefined
|
||||
}
|
||||
onClick={onSave}
|
||||
disabled={isSaving}
|
||||
className='w-full'
|
||||
/>
|
||||
<BaseButton
|
||||
small
|
||||
color='success'
|
||||
label={isSavingToStage ? 'Saving...' : 'Save to Stage'}
|
||||
subtitle={
|
||||
lastSavedToStageAt
|
||||
? dataFormatter.relativeTimestamp(lastSavedToStageAt)
|
||||
: undefined
|
||||
}
|
||||
onClick={onSaveToStage}
|
||||
disabled={isSavingToStage}
|
||||
className='w-full'
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiExitToApp}
|
||||
label='Exit'
|
||||
onClick={onExit}
|
||||
className='!text-red-700'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ConstructorMenu.displayName = 'ConstructorMenu';
|
||||
|
||||
export default ConstructorMenu;
|
||||
352
frontend/src/components/Constructor/ConstructorToolbar.tsx
Normal file
352
frontend/src/components/Constructor/ConstructorToolbar.tsx
Normal file
@ -0,0 +1,352 @@
|
||||
/**
|
||||
* ConstructorToolbar Component
|
||||
*
|
||||
* Unified toolbar combining page controls and element actions.
|
||||
* Glassmorphism styling with draggable positioning.
|
||||
*/
|
||||
|
||||
import React, { useState, useRef, useEffect, forwardRef } from 'react';
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiChevronDown,
|
||||
mdiImageMultiple,
|
||||
mdiViewCarousel,
|
||||
mdiTooltipText,
|
||||
mdiSwapHorizontal,
|
||||
mdiText,
|
||||
mdiPlus,
|
||||
mdiExitToApp,
|
||||
mdiChevronLeft,
|
||||
mdiChevronRight,
|
||||
mdiMusicNote,
|
||||
mdiVideo,
|
||||
} from '@mdi/js';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import BaseButton from '../BaseButton';
|
||||
import ClickOutside from '../ClickOutside';
|
||||
import PageSelector from './PageSelector';
|
||||
import InteractionModeToggle from './InteractionModeToggle';
|
||||
import MenuActionButton from './MenuActionButton';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import type { ConstructorToolbarProps } from './types';
|
||||
|
||||
const ConstructorToolbar = forwardRef<HTMLDivElement, ConstructorToolbarProps>(
|
||||
(
|
||||
{
|
||||
position,
|
||||
onDragStart,
|
||||
pages,
|
||||
activePageId,
|
||||
onPageChange,
|
||||
interactionMode,
|
||||
onModeChange,
|
||||
onSelectMenuItem,
|
||||
allowedNavigationTypes,
|
||||
onAddElement,
|
||||
onCreatePage,
|
||||
isCreatingPage,
|
||||
onSave,
|
||||
onSaveToStage,
|
||||
isSaving,
|
||||
isSavingToStage,
|
||||
lastSavedAt,
|
||||
lastSavedToStageAt,
|
||||
onExit,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
// Local UI state
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const [activeDropdown, setActiveDropdown] = useState<
|
||||
'bg' | 'elements' | null
|
||||
>(null);
|
||||
|
||||
// Refs for ClickOutside exclusion (following NavBarItem pattern)
|
||||
const bgTriggerRef = useRef<HTMLButtonElement>(null);
|
||||
const elementsTriggerRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
// Keyboard handling (Escape closes dropdown)
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && activeDropdown) {
|
||||
setActiveDropdown(null);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, [activeDropdown]);
|
||||
|
||||
// Dropdown handlers
|
||||
const closeDropdown = () => setActiveDropdown(null);
|
||||
const toggleDropdown = (dropdown: 'bg' | 'elements') => {
|
||||
setActiveDropdown((prev) => (prev === dropdown ? null : dropdown));
|
||||
};
|
||||
|
||||
// Close dropdown after menu action
|
||||
const handleMenuAction = (action: () => void) => {
|
||||
action();
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
// Shared button styles
|
||||
const triggerBtnClass =
|
||||
'flex items-center gap-1.5 px-3 py-2 rounded text-sm font-medium text-white/90 hover:bg-white/20 transition-colors';
|
||||
const dropdownPanelClass =
|
||||
'absolute top-full left-0 mt-1 min-w-[180px] py-1 rounded-lg bg-white/50 backdrop-blur-xl border border-white/30 shadow-lg z-10';
|
||||
|
||||
// Collapsed state
|
||||
if (isCollapsed) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className='fixed z-[1000] flex items-center gap-1 px-2 py-1.5 rounded-lg bg-white/10 backdrop-blur-xl border border-white/30 shadow-xl'
|
||||
style={{ left: position.x, top: position.y }}
|
||||
>
|
||||
<div
|
||||
className='cursor-move flex items-center justify-center w-10 h-10 text-white/60'
|
||||
onMouseDown={onDragStart}
|
||||
>
|
||||
<BaseIcon path={mdiDotsVertical} size={24} />
|
||||
</div>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setIsCollapsed(false)}
|
||||
className='flex items-center justify-center w-10 h-10 rounded text-white/60 hover:text-white/90 hover:bg-white/20 transition-colors'
|
||||
title='Expand toolbar'
|
||||
>
|
||||
<BaseIcon path={mdiChevronRight} size={26} />
|
||||
</button>
|
||||
<span className='text-base font-medium text-white/80 truncate max-w-[140px] pr-2'>
|
||||
{pages.find((p) => p.id === activePageId)?.name || 'Page'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className='fixed z-[1000] flex items-center gap-2 px-2 py-1.5 rounded-lg bg-white/10 backdrop-blur-xl border border-white/30 shadow-xl max-w-[95vw]'
|
||||
style={{ left: position.x, top: position.y }}
|
||||
>
|
||||
{/* Drag Handle */}
|
||||
<div
|
||||
className='cursor-move flex items-center justify-center w-10 h-10 text-white/60 hover:text-white/90'
|
||||
onMouseDown={onDragStart}
|
||||
>
|
||||
<BaseIcon path={mdiDotsVertical} size={24} />
|
||||
</div>
|
||||
|
||||
{/* Page Selector - reuse existing component */}
|
||||
<PageSelector
|
||||
pages={pages}
|
||||
activePageId={activePageId}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
|
||||
{/* Mode Toggle - reuse with compact=true */}
|
||||
<InteractionModeToggle
|
||||
mode={interactionMode}
|
||||
onModeChange={onModeChange}
|
||||
compact
|
||||
/>
|
||||
|
||||
{/* Divider */}
|
||||
<div className='w-px h-8 bg-white/30' />
|
||||
|
||||
{/* Backgrounds Dropdown */}
|
||||
<div className='relative'>
|
||||
<button
|
||||
ref={bgTriggerRef}
|
||||
type='button'
|
||||
onClick={() => toggleDropdown('bg')}
|
||||
className={triggerBtnClass}
|
||||
>
|
||||
<BaseIcon path={mdiImageMultiple} size={18} />
|
||||
<span>BG</span>
|
||||
<BaseIcon path={mdiChevronDown} size={16} />
|
||||
</button>
|
||||
{activeDropdown === 'bg' && (
|
||||
<ClickOutside
|
||||
onClickOutside={closeDropdown}
|
||||
excludedElements={[bgTriggerRef]}
|
||||
>
|
||||
<div className={dropdownPanelClass}>
|
||||
<MenuActionButton
|
||||
icon={mdiImageMultiple}
|
||||
label='Background Image'
|
||||
onClick={() =>
|
||||
handleMenuAction(() => onSelectMenuItem('background_image'))
|
||||
}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiVideo}
|
||||
label='Background Video'
|
||||
onClick={() =>
|
||||
handleMenuAction(() => onSelectMenuItem('background_video'))
|
||||
}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiMusicNote}
|
||||
label='Background Audio'
|
||||
onClick={() =>
|
||||
handleMenuAction(() => onSelectMenuItem('background_audio'))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</ClickOutside>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Elements Dropdown */}
|
||||
<div className='relative'>
|
||||
<button
|
||||
ref={elementsTriggerRef}
|
||||
type='button'
|
||||
onClick={() => toggleDropdown('elements')}
|
||||
className={triggerBtnClass}
|
||||
>
|
||||
<BaseIcon path={mdiPlus} size={18} />
|
||||
<span>Elements</span>
|
||||
<BaseIcon path={mdiChevronDown} size={16} />
|
||||
</button>
|
||||
{activeDropdown === 'elements' && (
|
||||
<ClickOutside
|
||||
onClickOutside={closeDropdown}
|
||||
excludedElements={[elementsTriggerRef]}
|
||||
>
|
||||
<div className={dropdownPanelClass}>
|
||||
<MenuActionButton
|
||||
icon={mdiSwapHorizontal}
|
||||
label='Navigation Button'
|
||||
onClick={() =>
|
||||
handleMenuAction(() =>
|
||||
onAddElement(allowedNavigationTypes[0]),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiSwapHorizontal}
|
||||
label='Transition'
|
||||
onClick={() =>
|
||||
handleMenuAction(() =>
|
||||
onSelectMenuItem('create_transition'),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiImageMultiple}
|
||||
label='Gallery'
|
||||
onClick={() =>
|
||||
handleMenuAction(() => onAddElement('gallery'))
|
||||
}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiViewCarousel}
|
||||
label='Carousel'
|
||||
onClick={() =>
|
||||
handleMenuAction(() => onAddElement('carousel'))
|
||||
}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiTooltipText}
|
||||
label='Tooltip'
|
||||
onClick={() =>
|
||||
handleMenuAction(() => onAddElement('tooltip'))
|
||||
}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiText}
|
||||
label='Description'
|
||||
onClick={() =>
|
||||
handleMenuAction(() => onAddElement('description'))
|
||||
}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiVideo}
|
||||
label='Video Player'
|
||||
onClick={() =>
|
||||
handleMenuAction(() => onAddElement('video_player'))
|
||||
}
|
||||
/>
|
||||
<MenuActionButton
|
||||
icon={mdiMusicNote}
|
||||
label='Audio Player'
|
||||
onClick={() =>
|
||||
handleMenuAction(() => onAddElement('audio_player'))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</ClickOutside>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className='w-px h-8 bg-white/30' />
|
||||
|
||||
{/* Create Page Button */}
|
||||
<button
|
||||
type='button'
|
||||
onClick={onCreatePage}
|
||||
disabled={isCreatingPage}
|
||||
className={`${triggerBtnClass} ${isCreatingPage ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
<BaseIcon path={mdiPlus} size={18} />
|
||||
<span>{isCreatingPage ? 'Creating...' : 'Page'}</span>
|
||||
</button>
|
||||
|
||||
{/* Save Button - reuse BaseButton with subtitle */}
|
||||
<BaseButton
|
||||
small
|
||||
color='info'
|
||||
label={isSaving ? 'Saving...' : 'Save'}
|
||||
subtitle={
|
||||
lastSavedAt
|
||||
? dataFormatter.relativeTimestamp(lastSavedAt)
|
||||
: undefined
|
||||
}
|
||||
onClick={onSave}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
|
||||
{/* Save to Stage Button */}
|
||||
<BaseButton
|
||||
small
|
||||
color='success'
|
||||
label={isSavingToStage ? 'Saving...' : 'Stage'}
|
||||
subtitle={
|
||||
lastSavedToStageAt
|
||||
? dataFormatter.relativeTimestamp(lastSavedToStageAt)
|
||||
: undefined
|
||||
}
|
||||
onClick={onSaveToStage}
|
||||
disabled={isSavingToStage}
|
||||
/>
|
||||
|
||||
{/* Exit Button */}
|
||||
<button
|
||||
type='button'
|
||||
onClick={onExit}
|
||||
className='flex items-center justify-center w-10 h-10 rounded text-red-400 hover:text-red-300 hover:bg-red-500/20 transition-colors'
|
||||
title='Exit constructor'
|
||||
>
|
||||
<BaseIcon path={mdiExitToApp} size={26} />
|
||||
</button>
|
||||
|
||||
{/* Collapse Toggle */}
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setIsCollapsed(true)}
|
||||
className='flex items-center justify-center w-10 h-10 rounded text-white/60 hover:text-white/90 hover:bg-white/20 transition-colors'
|
||||
title='Collapse toolbar'
|
||||
>
|
||||
<BaseIcon path={mdiChevronLeft} size={26} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ConstructorToolbar.displayName = 'ConstructorToolbar';
|
||||
|
||||
export default ConstructorToolbar;
|
||||
@ -36,8 +36,8 @@ const CreateTransitionForm: React.FC<CreateTransitionFormProps> = ({
|
||||
onSubmit,
|
||||
}) => {
|
||||
return (
|
||||
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
||||
<p className='text-[11px] font-semibold text-gray-600'>
|
||||
<div className='rounded border border-white/20 p-2 space-y-2'>
|
||||
<p className='text-[11px] font-semibold text-white/80'>
|
||||
Create next page transition
|
||||
</p>
|
||||
|
||||
@ -61,11 +61,11 @@ const CreateTransitionForm: React.FC<CreateTransitionFormProps> = ({
|
||||
))}
|
||||
</select>
|
||||
|
||||
<p className='text-[11px] text-gray-500'>
|
||||
<p className='text-[11px] text-white/60'>
|
||||
Transition duration is automatic from video metadata. {durationNote}
|
||||
</p>
|
||||
|
||||
<label className='flex items-center gap-2 text-[11px] text-gray-700'>
|
||||
<label className='flex items-center gap-2 text-[11px] text-white/80'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={supportsReverse}
|
||||
|
||||
@ -26,16 +26,16 @@ const ElementEditorHeader: React.FC<ElementEditorHeaderProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className='mb-3 flex items-center justify-between gap-2 cursor-move'
|
||||
className='mb-2 flex items-center justify-between gap-1 cursor-move'
|
||||
onMouseDown={onDragStart}
|
||||
>
|
||||
<p className='text-xs font-bold uppercase tracking-wide text-gray-700'>
|
||||
<p className='text-[10px] font-bold uppercase tracking-wide text-white/90 truncate'>
|
||||
{title}
|
||||
</p>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='flex items-center gap-1.5 shrink-0'>
|
||||
<button
|
||||
type='button'
|
||||
className='text-xs text-gray-700 hover:underline'
|
||||
className='text-[10px] text-white/70 hover:text-white hover:underline'
|
||||
onClick={onToggleCollapse}
|
||||
>
|
||||
{isCollapsed ? 'Expand' : 'Collapse'}
|
||||
@ -43,10 +43,10 @@ const ElementEditorHeader: React.FC<ElementEditorHeaderProps> = ({
|
||||
{showRemoveButton && (
|
||||
<button
|
||||
type='button'
|
||||
className='text-xs text-red-600 hover:underline'
|
||||
className='text-[10px] text-red-400 hover:text-red-300 hover:underline'
|
||||
onClick={onRemove}
|
||||
>
|
||||
Remove element
|
||||
Remove
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -183,7 +183,7 @@ export function ElementEditorPanel({
|
||||
return (
|
||||
<div
|
||||
ref={elementEditorRef}
|
||||
className={`fixed z-[1000] ${isCollapsed ? 'w-[260px]' : 'w-[380px]'} max-h-[calc(100vh-2rem)] overflow-auto rounded-lg border border-gray-200 bg-white/95 p-3 shadow-xl`}
|
||||
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
|
||||
@ -547,7 +547,7 @@ export function ElementEditorPanel({
|
||||
{/* 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'>
|
||||
<p className='text-[11px] font-semibold text-white/90'>
|
||||
Gallery Section Styles
|
||||
</p>
|
||||
<GallerySectionStyleInputs
|
||||
@ -692,7 +692,7 @@ export function ElementEditorPanel({
|
||||
showTitleStyles
|
||||
showAspectRatio
|
||||
/>
|
||||
<p className='text-[11px] font-semibold text-gray-700 pt-2'>
|
||||
<p className='text-[11px] font-semibold text-white/90 pt-2'>
|
||||
General Element Styles
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -10,23 +10,25 @@ import type { ConstructorInteractionMode } from './types';
|
||||
interface InteractionModeToggleProps {
|
||||
mode: ConstructorInteractionMode;
|
||||
onModeChange: (mode: ConstructorInteractionMode) => void;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const InteractionModeToggle: React.FC<InteractionModeToggleProps> = ({
|
||||
mode,
|
||||
onModeChange,
|
||||
compact = false,
|
||||
}) => {
|
||||
const isEditMode = mode === 'edit';
|
||||
|
||||
return (
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
<div className='inline-flex overflow-hidden rounded border border-gray-300 bg-white text-xs font-semibold'>
|
||||
<div className='inline-flex overflow-hidden rounded border border-white/30 bg-white/10 text-xs font-semibold'>
|
||||
<button
|
||||
type='button'
|
||||
className={`px-3 py-1.5 ${
|
||||
className={`px-3 py-1.5 transition-colors ${
|
||||
isEditMode
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'text-gray-700 hover:bg-gray-50'
|
||||
? 'bg-blue-500/80 text-white'
|
||||
: 'text-white/70 hover:bg-white/10'
|
||||
}`}
|
||||
onClick={() => onModeChange('edit')}
|
||||
>
|
||||
@ -34,21 +36,23 @@ const InteractionModeToggle: React.FC<InteractionModeToggleProps> = ({
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className={`border-l border-gray-300 px-3 py-1.5 ${
|
||||
className={`border-l border-white/30 px-3 py-1.5 transition-colors ${
|
||||
!isEditMode
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'text-gray-700 hover:bg-gray-50'
|
||||
? 'bg-blue-500/80 text-white'
|
||||
: 'text-white/70 hover:bg-white/10'
|
||||
}`}
|
||||
onClick={() => onModeChange('interact')}
|
||||
>
|
||||
Interact mode
|
||||
</button>
|
||||
</div>
|
||||
<span className='text-[11px] text-gray-600'>
|
||||
{isEditMode
|
||||
? 'Drag & configure elements.'
|
||||
: 'Click and interact with rendered elements.'}
|
||||
</span>
|
||||
{!compact && (
|
||||
<span className='text-[11px] text-white/60'>
|
||||
{isEditMode
|
||||
? 'Drag & configure elements.'
|
||||
: 'Click and interact with rendered elements.'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
* MenuActionButton Component
|
||||
*
|
||||
* Compact button for constructor menu actions.
|
||||
* Used in ConstructorMenu for adding elements, backgrounds, etc.
|
||||
* Used in ConstructorToolbar for adding elements, backgrounds, etc.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
@ -45,7 +45,10 @@ const PageSelector: React.FC<PageSelectorProps> = ({
|
||||
|
||||
return (
|
||||
<select
|
||||
className='rounded border border-gray-300 bg-white px-3 py-2 text-sm'
|
||||
className='rounded border border-white/30 bg-white/20 pl-3 pr-8 py-1.5 text-sm text-white/90 backdrop-blur-sm focus:outline-none focus:ring-1 focus:ring-white/40 appearance-none bg-no-repeat bg-[length:16px] bg-[right_8px_center]'
|
||||
style={{
|
||||
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.7)' d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z'/%3E%3C/svg%3E")`,
|
||||
}}
|
||||
value={activePageId ?? ''}
|
||||
onChange={(event) => onPageChange(event.target.value)}
|
||||
disabled={disabled}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* Types only - import components directly from their files:
|
||||
* import CanvasElement from './Constructor/CanvasElement';
|
||||
* import ConstructorMenu from './Constructor/ConstructorMenu';
|
||||
* import ConstructorToolbar from './Constructor/ConstructorToolbar';
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
|
||||
@ -83,6 +83,7 @@ export interface PageSelectorProps {
|
||||
export interface InteractionModeToggleProps {
|
||||
mode: ConstructorInteractionMode;
|
||||
onModeChange: (mode: ConstructorInteractionMode) => void;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -301,6 +302,48 @@ export interface ConstructorMenuProps {
|
||||
isSavingToStage: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified toolbar props - combines controls panel and menu functionality
|
||||
* Replaces ConstructorControlsPanelProps and ConstructorMenuProps
|
||||
*/
|
||||
export interface ConstructorToolbarProps {
|
||||
// Positioning (from useDraggable)
|
||||
position: Position;
|
||||
onDragStart: (event: React.MouseEvent) => void;
|
||||
|
||||
// Page selector (reuse PageSelector component)
|
||||
pages: TourPage[];
|
||||
activePageId: string;
|
||||
onPageChange: (pageId: string) => void;
|
||||
|
||||
// Mode toggle (reuse InteractionModeToggle with compact=true)
|
||||
interactionMode: ConstructorInteractionMode;
|
||||
onModeChange: (mode: ConstructorInteractionMode) => void;
|
||||
|
||||
// Background actions (opens ElementEditorPanel)
|
||||
onSelectMenuItem: (item: EditorMenuItem) => void;
|
||||
|
||||
// Element actions
|
||||
allowedNavigationTypes: NavigationElementType[];
|
||||
onAddElement: (type: CanvasElementType) => void;
|
||||
|
||||
// Page actions
|
||||
onCreatePage: () => void;
|
||||
isCreatingPage: boolean;
|
||||
|
||||
// Save actions (reuse BaseButton with subtitle for timestamps)
|
||||
onSave: () => void;
|
||||
onSaveToStage: () => void;
|
||||
isSaving: boolean;
|
||||
isSavingToStage: boolean;
|
||||
lastSavedAt?: string | null;
|
||||
lastSavedToStageAt?: string | null;
|
||||
|
||||
// Exit
|
||||
projectId: string;
|
||||
onExit: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu action button props
|
||||
*/
|
||||
|
||||
@ -71,14 +71,14 @@ const CarouselSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
<label
|
||||
htmlFor='carouselFullWidth'
|
||||
className='text-[11px] text-gray-700'
|
||||
className='text-[11px] text-white/80'
|
||||
>
|
||||
Full-width mode (background layer)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
||||
<p className='text-[11px] font-semibold text-gray-700'>
|
||||
<div className='rounded border border-white/20 p-2 space-y-2'>
|
||||
<p className='text-[11px] font-semibold text-white/90'>
|
||||
Navigation icons
|
||||
</p>
|
||||
|
||||
@ -177,7 +177,7 @@ const CarouselSettingsSectionCompact: React.FC<
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className='text-[10px] text-gray-600'>Caption font:</label>
|
||||
<label className='text-[10px] text-white/70'>Caption font:</label>
|
||||
<select
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={carouselCaptionFontFamily}
|
||||
@ -195,7 +195,7 @@ const CarouselSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
{carouselFullWidth && (
|
||||
<p className='text-[10px] text-gray-500 mt-1'>
|
||||
<p className='text-[10px] text-white/60 mt-1'>
|
||||
In full-width mode: set icon + dimensions for navigation-style
|
||||
buttons. Drag to reposition in editor.
|
||||
</p>
|
||||
@ -203,7 +203,7 @@ const CarouselSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div className='flex items-center justify-between'>
|
||||
<p className='text-[11px] font-semibold text-gray-600'>
|
||||
<p className='text-[11px] font-semibold text-white/80'>
|
||||
Carousel slides
|
||||
</p>
|
||||
<button
|
||||
@ -218,10 +218,10 @@ const CarouselSettingsSectionCompact: React.FC<
|
||||
{carouselSlides.map((slide, index) => (
|
||||
<div
|
||||
key={slide.id}
|
||||
className='rounded border border-gray-200 p-2 space-y-2'
|
||||
className='rounded border border-white/20 p-2 space-y-2'
|
||||
>
|
||||
<div className='flex items-center justify-between'>
|
||||
<p className='text-[11px] font-semibold text-gray-700'>
|
||||
<p className='text-[11px] font-semibold text-white/90'>
|
||||
Slide {index + 1}
|
||||
</p>
|
||||
<button
|
||||
@ -264,7 +264,7 @@ const CarouselSettingsSectionCompact: React.FC<
|
||||
))}
|
||||
|
||||
{carouselSlides.length === 0 && (
|
||||
<p className='text-[11px] text-gray-500'>
|
||||
<p className='text-[11px] text-white/60'>
|
||||
No slides yet. Click "+ Add slide" to create one.
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -16,25 +16,25 @@ const CommonSettingsSectionCompact: React.FC<CommonSettingsSectionProps> = ({
|
||||
showLabel = true,
|
||||
}) => {
|
||||
return (
|
||||
<div className='mb-2 space-y-2'>
|
||||
<div className='mb-1.5 space-y-1.5'>
|
||||
{showLabel && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-0.5 block text-[10px] font-semibold text-white/80'>
|
||||
Label
|
||||
</label>
|
||||
<input
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
className='w-full rounded border border-gray-300 px-1.5 py-0.5 text-[11px]'
|
||||
value={label}
|
||||
onChange={(event) => onChange('label', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-0.5 block text-[10px] font-semibold text-white/80'>
|
||||
Appear delay (sec)
|
||||
</label>
|
||||
<input
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
className='w-full rounded border border-gray-300 px-1.5 py-0.5 text-[11px]'
|
||||
type='number'
|
||||
min='0'
|
||||
step='0.1'
|
||||
@ -43,11 +43,11 @@ const CommonSettingsSectionCompact: React.FC<CommonSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-0.5 block text-[10px] font-semibold text-white/80'>
|
||||
Appear duration (sec)
|
||||
</label>
|
||||
<input
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
className='w-full rounded border border-gray-300 px-1.5 py-0.5 text-[11px]'
|
||||
type='number'
|
||||
min='0.1'
|
||||
step='0.1'
|
||||
@ -57,7 +57,7 @@ const CommonSettingsSectionCompact: React.FC<CommonSettingsSectionProps> = ({
|
||||
onChange('appearDurationSec', event.target.value)
|
||||
}
|
||||
/>
|
||||
<p className='mt-1 text-[11px] text-gray-500'>
|
||||
<p className='mt-0.5 text-[10px] text-white/60'>
|
||||
Leave empty for unlimited.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -42,7 +42,7 @@ const DescriptionSettingsSectionCompact: React.FC<
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Icon
|
||||
</label>
|
||||
<select
|
||||
@ -64,7 +64,7 @@ const DescriptionSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Description title
|
||||
</label>
|
||||
<input
|
||||
@ -75,7 +75,7 @@ const DescriptionSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Description text
|
||||
</label>
|
||||
<textarea
|
||||
@ -87,7 +87,7 @@ const DescriptionSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Title font size (px)
|
||||
</label>
|
||||
<input
|
||||
@ -101,7 +101,7 @@ const DescriptionSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Text font size (px)
|
||||
</label>
|
||||
<input
|
||||
@ -115,7 +115,7 @@ const DescriptionSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Title font family
|
||||
</label>
|
||||
<select
|
||||
@ -135,7 +135,7 @@ const DescriptionSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Text font family
|
||||
</label>
|
||||
<select
|
||||
@ -155,7 +155,7 @@ const DescriptionSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Title color
|
||||
</label>
|
||||
<input
|
||||
@ -169,7 +169,7 @@ const DescriptionSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Text color
|
||||
</label>
|
||||
<input
|
||||
|
||||
@ -24,12 +24,12 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
<div className='space-y-3'>
|
||||
{/* Appear Animation */}
|
||||
<div>
|
||||
<p className='mb-2 text-[11px] font-semibold text-gray-700'>
|
||||
<p className='mb-2 text-[11px] font-semibold text-white/90'>
|
||||
Appear Animation
|
||||
</p>
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
<div className='col-span-2'>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>Type</label>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>Type</label>
|
||||
<select
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={values.appearAnimation || ''}
|
||||
@ -45,7 +45,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Duration (sec)
|
||||
</label>
|
||||
<input
|
||||
@ -58,7 +58,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Easing
|
||||
</label>
|
||||
<select
|
||||
@ -80,12 +80,12 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
|
||||
{/* Hover Effects */}
|
||||
<div>
|
||||
<p className='mb-2 text-[11px] font-semibold text-gray-700'>
|
||||
<p className='mb-2 text-[11px] font-semibold text-white/90'>
|
||||
Hover Effects
|
||||
</p>
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Scale
|
||||
</label>
|
||||
<input
|
||||
@ -96,7 +96,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Opacity
|
||||
</label>
|
||||
<input
|
||||
@ -107,7 +107,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
BG color
|
||||
</label>
|
||||
<input
|
||||
@ -118,7 +118,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Text color
|
||||
</label>
|
||||
<input
|
||||
@ -129,7 +129,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div className='col-span-2'>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Box shadow
|
||||
</label>
|
||||
<input
|
||||
@ -140,7 +140,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div className='col-span-2'>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Transition (sec)
|
||||
</label>
|
||||
<input
|
||||
@ -157,12 +157,12 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
|
||||
{/* Focus Effects */}
|
||||
<div>
|
||||
<p className='mb-2 text-[11px] font-semibold text-gray-700'>
|
||||
<p className='mb-2 text-[11px] font-semibold text-white/90'>
|
||||
Focus Effects
|
||||
</p>
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Scale
|
||||
</label>
|
||||
<input
|
||||
@ -173,7 +173,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Opacity
|
||||
</label>
|
||||
<input
|
||||
@ -184,7 +184,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Outline
|
||||
</label>
|
||||
<input
|
||||
@ -195,7 +195,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Box shadow
|
||||
</label>
|
||||
<input
|
||||
@ -210,12 +210,12 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
|
||||
{/* Active/Press Effects */}
|
||||
<div>
|
||||
<p className='mb-2 text-[11px] font-semibold text-gray-700'>
|
||||
<p className='mb-2 text-[11px] font-semibold text-white/90'>
|
||||
Active/Press Effects
|
||||
</p>
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Scale
|
||||
</label>
|
||||
<input
|
||||
@ -226,7 +226,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Opacity
|
||||
</label>
|
||||
<input
|
||||
@ -237,7 +237,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div className='col-span-2'>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
BG color
|
||||
</label>
|
||||
<input
|
||||
@ -254,17 +254,17 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
|
||||
{/* Slide Transition Override - Gallery/Carousel only */}
|
||||
{showSlideTransition && (
|
||||
<div className='mt-3 border-t border-gray-200 pt-3'>
|
||||
<p className='mb-1 text-[11px] font-semibold text-gray-700'>
|
||||
<div className='mt-3 border-t border-white/20 pt-3'>
|
||||
<p className='mb-1 text-[11px] font-semibold text-white/90'>
|
||||
Slide Transition
|
||||
</p>
|
||||
<p className='mb-2 text-[10px] text-gray-500'>
|
||||
<p className='mb-2 text-[10px] text-white/60'>
|
||||
Override page transition for slides. Leave empty for defaults.
|
||||
</p>
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
{/* Type */}
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Type
|
||||
</label>
|
||||
<select
|
||||
@ -282,7 +282,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
|
||||
{/* Duration */}
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Duration (ms)
|
||||
</label>
|
||||
<input
|
||||
@ -300,7 +300,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
|
||||
{/* Easing */}
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Easing
|
||||
</label>
|
||||
<select
|
||||
@ -320,7 +320,7 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
|
||||
{/* Overlay Color */}
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-500'>
|
||||
<label className='mb-1 block text-[10px] text-white/60'>
|
||||
Overlay Color
|
||||
</label>
|
||||
<div className='flex gap-1'>
|
||||
|
||||
@ -42,15 +42,15 @@ export const ElementSettingsTabsCompact: React.FC<ElementSettingsTabsProps> = ({
|
||||
tabs,
|
||||
}) => {
|
||||
return (
|
||||
<div className='mb-3 inline-flex w-full overflow-hidden rounded border border-gray-300'>
|
||||
<div className='mb-2 inline-flex w-full overflow-hidden rounded border border-white/30'>
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
type='button'
|
||||
className={`flex-1 px-2 py-1.5 text-[11px] font-semibold transition-colors ${
|
||||
className={`flex-1 px-1.5 py-1 text-[10px] font-semibold transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-white text-gray-700 hover:bg-gray-50'
|
||||
: 'bg-white/20 text-white/80 hover:bg-white/30'
|
||||
}`}
|
||||
onClick={() => onTabChange(tab.id)}
|
||||
>
|
||||
|
||||
@ -54,13 +54,13 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
||||
}) => {
|
||||
return (
|
||||
<div className='mt-3 space-y-2'>
|
||||
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
||||
<p className='text-[11px] font-semibold text-gray-700'>
|
||||
<div className='rounded border border-white/20 p-2 space-y-2'>
|
||||
<p className='text-[11px] font-semibold text-white/90'>
|
||||
Carousel navigation
|
||||
</p>
|
||||
|
||||
{/* Previous button */}
|
||||
<p className='text-[10px] font-medium text-gray-600 mt-1'>Previous</p>
|
||||
<p className='text-[10px] font-medium text-white/70 mt-1'>Previous</p>
|
||||
<select
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={prevIconUrl}
|
||||
@ -111,7 +111,7 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
||||
)}
|
||||
|
||||
{/* Next button */}
|
||||
<p className='text-[10px] font-medium text-gray-600 mt-1'>Next</p>
|
||||
<p className='text-[10px] font-medium text-white/70 mt-1'>Next</p>
|
||||
<select
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={nextIconUrl}
|
||||
@ -162,7 +162,7 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
||||
)}
|
||||
|
||||
{/* Back button */}
|
||||
<p className='text-[10px] font-medium text-gray-600 mt-1'>Back</p>
|
||||
<p className='text-[10px] font-medium text-white/70 mt-1'>Back</p>
|
||||
<select
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={backIconUrl}
|
||||
@ -221,7 +221,7 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
)}
|
||||
|
||||
<p className='text-[10px] text-gray-500 mt-1'>
|
||||
<p className='text-[10px] text-white/60 mt-1'>
|
||||
Set icon + dimensions for navigation-style buttons. Drag to
|
||||
reposition.
|
||||
</p>
|
||||
|
||||
@ -41,13 +41,13 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
showTextAlign = 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='rounded border border-white/20 p-2 space-y-2'>
|
||||
<p className='text-[11px] font-semibold text-white/90'>{sectionLabel}</p>
|
||||
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
{/* Background Color */}
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
BG color
|
||||
</label>
|
||||
<input
|
||||
@ -63,7 +63,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Text Color (not for wrapper) */}
|
||||
{prefix !== 'galleryWrapper' && !showTitleStyles && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Text color
|
||||
</label>
|
||||
<input
|
||||
@ -77,7 +77,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
|
||||
{/* Padding */}
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Padding
|
||||
</label>
|
||||
<input
|
||||
@ -90,7 +90,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
|
||||
{/* Border Radius */}
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>Radius</label>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>Radius</label>
|
||||
<input
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={values[`${prefix}BorderRadius`] || ''}
|
||||
@ -101,7 +101,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
|
||||
{/* Border */}
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>Border</label>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>Border</label>
|
||||
<input
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={values[`${prefix}Border`] || ''}
|
||||
@ -113,7 +113,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Text Alignment (optional) */}
|
||||
{showTextAlign && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Align
|
||||
</label>
|
||||
<select
|
||||
@ -131,7 +131,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Gap (optional) */}
|
||||
{showGap && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>Gap</label>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>Gap</label>
|
||||
<input
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={values[`${prefix}Gap`] || ''}
|
||||
@ -144,7 +144,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Backdrop Blur (wrapper only) */}
|
||||
{showBlur && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>Blur</label>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>Blur</label>
|
||||
<input
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={values[`${prefix}BackdropBlur`] || ''}
|
||||
@ -159,7 +159,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Grid Columns (optional) */}
|
||||
{showColumns && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Columns
|
||||
</label>
|
||||
<input
|
||||
@ -179,7 +179,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Font Size (optional) */}
|
||||
{showFont && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Font size
|
||||
</label>
|
||||
<input
|
||||
@ -194,7 +194,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Font Weight (optional) */}
|
||||
{showFont && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Font weight
|
||||
</label>
|
||||
<input
|
||||
@ -210,7 +210,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Font Family (optional - full width) */}
|
||||
{showFont && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>Font</label>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>Font</label>
|
||||
<select
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={values[`${prefix}FontFamily`] || ''}
|
||||
@ -229,10 +229,10 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Header Dimensions (header section only) */}
|
||||
{showDimensions && (
|
||||
<>
|
||||
<p className='text-[10px] text-gray-500 pt-1'>Dimensions:</p>
|
||||
<p className='text-[10px] text-white/60 pt-1'>Dimensions:</p>
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Width
|
||||
</label>
|
||||
<input
|
||||
@ -243,7 +243,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Height
|
||||
</label>
|
||||
<input
|
||||
@ -254,7 +254,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Min Height
|
||||
</label>
|
||||
<input
|
||||
@ -265,7 +265,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Max Height
|
||||
</label>
|
||||
<input
|
||||
@ -282,10 +282,10 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Card Aspect Ratio and Min Height (cards only) */}
|
||||
{showAspectRatio && (
|
||||
<>
|
||||
<p className='text-[10px] text-gray-500 pt-1'>Card dimensions:</p>
|
||||
<p className='text-[10px] text-white/60 pt-1'>Card dimensions:</p>
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Aspect Ratio
|
||||
</label>
|
||||
<select
|
||||
@ -303,7 +303,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Min Height
|
||||
</label>
|
||||
<input
|
||||
@ -320,10 +320,10 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
{/* Card Title Styles (cards only) */}
|
||||
{showTitleStyles && (
|
||||
<>
|
||||
<p className='text-[10px] text-gray-500 pt-1'>Card title overlay:</p>
|
||||
<p className='text-[10px] text-white/60 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'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Title color
|
||||
</label>
|
||||
<input
|
||||
@ -336,7 +336,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Title BG
|
||||
</label>
|
||||
<input
|
||||
@ -349,7 +349,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Title size
|
||||
</label>
|
||||
<input
|
||||
@ -362,7 +362,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Title weight
|
||||
</label>
|
||||
<input
|
||||
@ -376,7 +376,7 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||
<label className='mb-1 block text-[10px] text-white/70'>
|
||||
Title shadow
|
||||
</label>
|
||||
<input
|
||||
|
||||
@ -60,8 +60,8 @@ const GallerySettingsSectionCompact: React.FC<
|
||||
return (
|
||||
<div className='space-y-3'>
|
||||
{/* Header Settings */}
|
||||
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
||||
<p className='text-[11px] font-semibold text-gray-700'>
|
||||
<div className='rounded border border-white/20 p-2 space-y-2'>
|
||||
<p className='text-[11px] font-semibold text-white/90'>
|
||||
Gallery header
|
||||
</p>
|
||||
|
||||
@ -104,9 +104,9 @@ const GallerySettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
{/* Info Spans */}
|
||||
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
||||
<div className='rounded border border-white/20 p-2 space-y-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<p className='text-[11px] font-semibold text-gray-700'>Info spans</p>
|
||||
<p className='text-[11px] font-semibold text-white/90'>Info spans</p>
|
||||
<button
|
||||
type='button'
|
||||
className='text-xs text-blue-700 hover:underline'
|
||||
@ -157,7 +157,7 @@ const GallerySettingsSectionCompact: React.FC<
|
||||
))}
|
||||
|
||||
{galleryInfoSpans.length === 0 && (
|
||||
<p className='text-[10px] text-gray-500'>
|
||||
<p className='text-[10px] text-white/60'>
|
||||
Add spans for brief notes (capacity, price, icons, etc.)
|
||||
</p>
|
||||
)}
|
||||
@ -165,7 +165,7 @@ const GallerySettingsSectionCompact: React.FC<
|
||||
|
||||
{/* Gallery Cards */}
|
||||
<div className='flex items-center justify-between'>
|
||||
<p className='text-[11px] font-semibold text-gray-600'>Gallery cards</p>
|
||||
<p className='text-[11px] font-semibold text-white/80'>Gallery cards</p>
|
||||
<button
|
||||
type='button'
|
||||
className='text-xs text-blue-700 hover:underline'
|
||||
@ -178,10 +178,10 @@ const GallerySettingsSectionCompact: React.FC<
|
||||
{galleryCards.map((card, index) => (
|
||||
<div
|
||||
key={card.id}
|
||||
className='rounded border border-gray-200 p-2 space-y-2'
|
||||
className='rounded border border-white/20 p-2 space-y-2'
|
||||
>
|
||||
<div className='flex items-center justify-between'>
|
||||
<p className='text-[11px] font-semibold text-gray-700'>
|
||||
<p className='text-[11px] font-semibold text-white/90'>
|
||||
Card {index + 1}
|
||||
</p>
|
||||
<button
|
||||
@ -234,7 +234,7 @@ const GallerySettingsSectionCompact: React.FC<
|
||||
))}
|
||||
|
||||
{galleryCards.length === 0 && (
|
||||
<p className='text-[11px] text-gray-500'>
|
||||
<p className='text-[11px] text-white/60'>
|
||||
No cards yet. Click "+ Add card" to create one.
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -39,7 +39,7 @@ const MediaSettingsSectionCompact: React.FC<
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
{assetLabel}
|
||||
</label>
|
||||
<select
|
||||
@ -60,7 +60,7 @@ const MediaSettingsSectionCompact: React.FC<
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label className='flex items-center gap-2 text-[11px] text-gray-700'>
|
||||
<label className='flex items-center gap-2 text-[11px] text-white/80'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={mediaAutoplay}
|
||||
@ -69,7 +69,7 @@ const MediaSettingsSectionCompact: React.FC<
|
||||
Autoplay
|
||||
</label>
|
||||
|
||||
<label className='flex items-center gap-2 text-[11px] text-gray-700'>
|
||||
<label className='flex items-center gap-2 text-[11px] text-white/80'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={mediaLoop}
|
||||
@ -79,7 +79,7 @@ const MediaSettingsSectionCompact: React.FC<
|
||||
</label>
|
||||
|
||||
{mediaType === 'video' && (
|
||||
<label className='flex items-center gap-2 text-[11px] text-gray-700'>
|
||||
<label className='flex items-center gap-2 text-[11px] text-white/80'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={mediaMuted}
|
||||
|
||||
@ -107,7 +107,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Type
|
||||
</label>
|
||||
<select
|
||||
@ -140,7 +140,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Button text
|
||||
</label>
|
||||
<input
|
||||
@ -151,7 +151,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Label font
|
||||
</label>
|
||||
<select
|
||||
@ -171,7 +171,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='flex items-center gap-2 text-[11px] font-semibold text-gray-600'>
|
||||
<label className='flex items-center gap-2 text-[11px] font-semibold text-white/80'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={navDisabled}
|
||||
@ -182,7 +182,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Icon
|
||||
</label>
|
||||
<select
|
||||
@ -202,7 +202,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
))}
|
||||
</select>
|
||||
{selectedMediaDurationNote && (
|
||||
<p className='mt-1 text-[11px] text-gray-500'>
|
||||
<p className='mt-1 text-[11px] text-white/60'>
|
||||
{selectedMediaDurationNote}
|
||||
</p>
|
||||
)}
|
||||
@ -210,7 +210,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
|
||||
{/* Back navigation info text */}
|
||||
{currentKind === 'back' && (
|
||||
<p className='text-[11px] italic text-gray-500'>
|
||||
<p className='text-[11px] italic text-white/60'>
|
||||
Back button returns to the previous page using the original forward
|
||||
transition in reverse.
|
||||
</p>
|
||||
@ -220,7 +220,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
{currentKind === 'forward' && (
|
||||
<>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Target page
|
||||
</label>
|
||||
<select
|
||||
@ -243,7 +243,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Transition video asset
|
||||
</label>
|
||||
<select
|
||||
@ -265,14 +265,14 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
))}
|
||||
</select>
|
||||
{selectedTransitionDurationNote && (
|
||||
<p className='mt-1 text-[11px] text-gray-500'>
|
||||
<p className='mt-1 text-[11px] text-white/60'>
|
||||
{selectedTransitionDurationNote}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Back transition mode
|
||||
</label>
|
||||
<select
|
||||
@ -298,7 +298,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
|
||||
{transitionReverseMode === 'separate_video' && (
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Back transition video asset
|
||||
</label>
|
||||
<select
|
||||
@ -325,11 +325,11 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
{/* CSS Transition Settings (when no video selected) */}
|
||||
{!transitionVideoUrl && (
|
||||
<>
|
||||
<p className='mt-2 text-[11px] italic text-gray-500'>
|
||||
<p className='mt-2 text-[11px] italic text-white/60'>
|
||||
No transition video selected. Configure CSS transition instead:
|
||||
</p>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Transition type
|
||||
</label>
|
||||
<select
|
||||
@ -347,7 +347,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Duration (ms)
|
||||
</label>
|
||||
<input
|
||||
@ -366,7 +366,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Easing
|
||||
</label>
|
||||
<select
|
||||
@ -384,7 +384,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Overlay color
|
||||
</label>
|
||||
<div className='flex gap-2'>
|
||||
@ -411,7 +411,7 @@ const NavigationSettingsSectionCompact: React.FC<
|
||||
)}
|
||||
|
||||
{transitionVideoUrl && (
|
||||
<p className='text-[11px] text-gray-500'>
|
||||
<p className='text-[11px] text-white/60'>
|
||||
Transition duration is set automatically from the selected video.
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -14,12 +14,12 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
<p className='text-[10px] text-gray-500'>
|
||||
<p className='text-[10px] text-white/60'>
|
||||
Dimensions = % of canvas, border/radius = px
|
||||
</p>
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Width (%)
|
||||
</label>
|
||||
<input
|
||||
@ -33,7 +33,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Height (%)
|
||||
</label>
|
||||
<input
|
||||
@ -47,7 +47,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Min W (%)
|
||||
</label>
|
||||
<input
|
||||
@ -60,7 +60,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Max W (%)
|
||||
</label>
|
||||
<input
|
||||
@ -73,7 +73,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Min H (%)
|
||||
</label>
|
||||
<input
|
||||
@ -86,7 +86,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Max H (%)
|
||||
</label>
|
||||
<input
|
||||
@ -99,7 +99,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Margin
|
||||
</label>
|
||||
<input
|
||||
@ -110,7 +110,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Padding
|
||||
</label>
|
||||
<input
|
||||
@ -121,7 +121,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Gap (rem)
|
||||
</label>
|
||||
<input
|
||||
@ -135,7 +135,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Font size
|
||||
</label>
|
||||
<input
|
||||
@ -146,7 +146,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Line height
|
||||
</label>
|
||||
<input
|
||||
@ -156,7 +156,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Font weight
|
||||
</label>
|
||||
<input
|
||||
@ -167,7 +167,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Border (px)
|
||||
</label>
|
||||
<input
|
||||
@ -181,7 +181,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Radius (px)
|
||||
</label>
|
||||
<input
|
||||
@ -195,7 +195,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Opacity
|
||||
</label>
|
||||
<input
|
||||
@ -206,7 +206,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
z-index
|
||||
</label>
|
||||
<input
|
||||
@ -219,7 +219,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Box shadow
|
||||
</label>
|
||||
<input
|
||||
@ -232,7 +232,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Display
|
||||
</label>
|
||||
<select
|
||||
@ -250,7 +250,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Position
|
||||
</label>
|
||||
<select
|
||||
@ -267,7 +267,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Justify
|
||||
</label>
|
||||
<select
|
||||
@ -285,7 +285,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Align
|
||||
</label>
|
||||
<select
|
||||
@ -302,7 +302,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Text align
|
||||
</label>
|
||||
<select
|
||||
@ -318,7 +318,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
BG color
|
||||
</label>
|
||||
<input
|
||||
@ -329,7 +329,7 @@ const StyleSettingsSectionCompact: React.FC<StyleSettingsSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Text color
|
||||
</label>
|
||||
<input
|
||||
|
||||
@ -34,7 +34,7 @@ const TooltipSettingsSectionCompact: React.FC<
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Icon
|
||||
</label>
|
||||
<select
|
||||
@ -56,7 +56,7 @@ const TooltipSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Tooltip title
|
||||
</label>
|
||||
<input
|
||||
@ -67,7 +67,7 @@ const TooltipSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Tooltip text
|
||||
</label>
|
||||
<textarea
|
||||
@ -79,7 +79,7 @@ const TooltipSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Title font family
|
||||
</label>
|
||||
<select
|
||||
@ -99,7 +99,7 @@ const TooltipSettingsSectionCompact: React.FC<
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>
|
||||
<label className='mb-1 block text-[11px] font-semibold text-white/80'>
|
||||
Text font family
|
||||
</label>
|
||||
<select
|
||||
|
||||
@ -280,7 +280,7 @@ export function useConstructorPageActions({
|
||||
background_audio_url: '',
|
||||
background_loop: false,
|
||||
requires_auth: false,
|
||||
ui_schema_json: JSON.stringify({ elements: [] }),
|
||||
ui_schema_json: { elements: [] },
|
||||
// Copy project design dimensions to new page
|
||||
design_width: project?.design_width ?? null,
|
||||
design_height: project?.design_height ?? null,
|
||||
|
||||
@ -210,8 +210,8 @@ export const createDefaultElement = (
|
||||
id: createLocalId(),
|
||||
type,
|
||||
label: ELEMENT_TYPE_LABELS[type] || type,
|
||||
xPercent: clamp(12 + index * 4, 5, 80),
|
||||
yPercent: clamp(16 + index * 6, 8, 85),
|
||||
xPercent: clamp(45 + index * 3, 10, 85), // Center horizontally
|
||||
yPercent: clamp(45 + index * 4, 15, 80), // Center vertically
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
};
|
||||
|
||||
@ -20,15 +20,30 @@ export const parseJsonObject = <T>(value?: unknown, fallback?: T): T => {
|
||||
if (!value) return (fallback || ({} as T)) as T;
|
||||
|
||||
try {
|
||||
if (typeof value === 'string') {
|
||||
const parsed = JSON.parse(value);
|
||||
return (parsed || fallback || {}) as T;
|
||||
let result: unknown = value;
|
||||
|
||||
// Handle string input - parse JSON
|
||||
if (typeof result === 'string') {
|
||||
result = JSON.parse(result);
|
||||
|
||||
// Handle double-encoded JSON (string that parses to another string)
|
||||
// This can happen if JSON was stringified twice
|
||||
while (typeof result === 'string') {
|
||||
try {
|
||||
result = JSON.parse(result);
|
||||
} catch {
|
||||
// Not valid JSON, use fallback
|
||||
return (fallback || ({} as T)) as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return value as T;
|
||||
// Ensure we return an object, not a primitive
|
||||
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
||||
return result as T;
|
||||
}
|
||||
|
||||
// If it's an array or primitive, return fallback
|
||||
return (fallback || ({} as T)) as T;
|
||||
} catch {
|
||||
return (fallback || ({} as T)) as T;
|
||||
|
||||
@ -14,8 +14,7 @@ import { flushSync } from 'react-dom';
|
||||
import BaseButton from '../components/BaseButton';
|
||||
import CanvasBackground from '../components/Constructor/CanvasBackground';
|
||||
import TransitionBlackOverlay from '../components/TransitionBlackOverlay';
|
||||
import ConstructorControlsPanel from '../components/Constructor/ConstructorControlsPanel';
|
||||
import ConstructorMenu from '../components/Constructor/ConstructorMenu';
|
||||
import ConstructorToolbar from '../components/Constructor/ConstructorToolbar';
|
||||
import TransitionPreviewOverlay from '../components/Constructor/TransitionPreviewOverlay';
|
||||
import CanvasElementComponent from '../components/Constructor/CanvasElement';
|
||||
import GalleryCarouselOverlay from '../components/UiElements/GalleryCarouselOverlay';
|
||||
@ -133,7 +132,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
);
|
||||
const canvasRef = useRef<HTMLDivElement>(null);
|
||||
const elementEditorRef = useRef<HTMLDivElement>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||
const [isAuthReady, setIsAuthReady] = useState(false);
|
||||
const isElementEditMode = mode === 'element_edit';
|
||||
|
||||
@ -278,7 +277,8 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
|
||||
const [constructorInteractionMode, setConstructorInteractionMode] =
|
||||
useState<ConstructorInteractionMode>('edit');
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
// Note: isMenuOpen kept for context backward compatibility, not used by ConstructorToolbar
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
||||
const [isEditorCollapsed, setIsEditorCollapsed] = useState(false);
|
||||
const [elementEditorTab, setElementEditorTab] = useState<
|
||||
'general' | 'css' | 'effects'
|
||||
@ -353,27 +353,17 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
}, [activeGalleryCarousel, elements]);
|
||||
|
||||
// Draggable panels using useDraggable hook
|
||||
const {
|
||||
position: constructorControlsPosition,
|
||||
onDragStart: onConstructorControlsDragStart,
|
||||
} = useDraggable({
|
||||
initialPosition: { x: 20, y: 20 },
|
||||
elementWidth: 460,
|
||||
elementHeight: 64,
|
||||
});
|
||||
|
||||
const { position: menuPosition, onDragStart: onMenuDragStart } = useDraggable(
|
||||
{
|
||||
initialPosition: { x: 9999, y: 10 }, // Top right corner (x will be clamped)
|
||||
elementWidth: 240,
|
||||
elementHeight: 60,
|
||||
},
|
||||
);
|
||||
const { position: toolbarPosition, onDragStart: onToolbarDragStart } =
|
||||
useDraggable({
|
||||
initialPosition: { x: 20, y: 20 },
|
||||
elementWidth: 200, // Use collapsed width for bounds - expanded can go off-screen
|
||||
elementHeight: 56,
|
||||
});
|
||||
|
||||
const { position: editorPosition, onDragStart: onElementEditorDragStart } =
|
||||
useDraggable({
|
||||
initialPosition: { x: 0, y: 72 },
|
||||
elementWidth: isEditorCollapsed ? 260 : 380,
|
||||
initialPosition: { x: 9999, y: 0 }, // Top-right corner (x will be clamped)
|
||||
elementWidth: isEditorCollapsed ? 220 : 300,
|
||||
elementHeight: 60,
|
||||
});
|
||||
|
||||
@ -1141,7 +1131,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
// Ignore clicks on menu to allow menu item selection
|
||||
useOutsideClick({
|
||||
containerRef: elementEditorRef,
|
||||
ignoreRefs: [menuRef],
|
||||
ignoreRefs: [toolbarRef],
|
||||
ignoreDataAttribute: 'data-constructor-element-id',
|
||||
selectedValue: selectedElementId,
|
||||
onOutsideClick: useCallback(() => {
|
||||
@ -1648,18 +1638,37 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
</div>
|
||||
|
||||
{pages.length > 0 && !isElementEditMode && (
|
||||
<ConstructorControlsPanel
|
||||
projectId={projectId}
|
||||
<ConstructorToolbar
|
||||
ref={toolbarRef}
|
||||
position={toolbarPosition}
|
||||
onDragStart={onToolbarDragStart}
|
||||
pages={pages}
|
||||
activePageId={activePageId}
|
||||
interactionMode={constructorInteractionMode}
|
||||
position={constructorControlsPosition}
|
||||
onPageChange={(pageId) => {
|
||||
const page = pages.find((p) => p.id === pageId);
|
||||
if (page) switchToPage(page);
|
||||
}}
|
||||
interactionMode={constructorInteractionMode}
|
||||
onModeChange={setConstructorInteractionMode}
|
||||
onDragStart={onConstructorControlsDragStart}
|
||||
onSelectMenuItem={selectMenuItemForEdit}
|
||||
allowedNavigationTypes={allowedNavigationTypes}
|
||||
onAddElement={addElement}
|
||||
onCreatePage={createPage}
|
||||
isCreatingPage={isCreatingPage}
|
||||
onSave={saveConstructor}
|
||||
onSaveToStage={handleSaveToStage}
|
||||
isSaving={isSaving}
|
||||
isSavingToStage={isSavingToStage}
|
||||
lastSavedAt={lastProjectSaveAt}
|
||||
lastSavedToStageAt={lastSavedToStage}
|
||||
projectId={projectId}
|
||||
onExit={() =>
|
||||
router.push(
|
||||
projectId
|
||||
? `/projects/${projectId}`
|
||||
: '/projects/projects-list',
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -1801,34 +1810,6 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
title={editorTitle}
|
||||
/>
|
||||
)}
|
||||
|
||||
{pages.length > 0 && !isElementEditMode && (
|
||||
<ConstructorMenu
|
||||
ref={menuRef}
|
||||
position={menuPosition}
|
||||
isOpen={isMenuOpen}
|
||||
allowedNavigationTypes={allowedNavigationTypes}
|
||||
isCreatingPage={isCreatingPage}
|
||||
isSaving={isSaving}
|
||||
isSavingToStage={isSavingToStage}
|
||||
onDragStart={onMenuDragStart}
|
||||
onToggleOpen={() => setIsMenuOpen((prev) => !prev)}
|
||||
onSelectMenuItem={selectMenuItemForEdit}
|
||||
onAddElement={addElement}
|
||||
onCreatePage={createPage}
|
||||
onSave={saveConstructor}
|
||||
onSaveToStage={handleSaveToStage}
|
||||
onExit={() =>
|
||||
router.push(
|
||||
projectId
|
||||
? `/projects/${projectId}`
|
||||
: '/projects/projects-list',
|
||||
)
|
||||
}
|
||||
lastSavedAt={lastProjectSaveAt}
|
||||
lastSavedToStageAt={lastSavedToStage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TransitionPreviewOverlay
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user