Files
site-builder/craft/src/panels/right/styles/MediaStylePanel.tsx
T

199 lines
8.2 KiB
TypeScript
Raw Normal View History

import React from 'react';
import { useEditor } from '@craftjs/core';
import {
BG_COLORS,
SPACING_PRESETS,
RADIUS_PRESETS,
} from '../../../constants/presets';
import {
StylePanelProps,
SectionLabel,
ColorSwatchGrid,
PresetButtonGrid,
CollapsibleSection,
ColorPickerField,
ArrayPropEditor,
labelStyle,
inputStyle,
smallInputStyle,
sectionGap,
useNodeProp,
} from './shared';
import { AssetPicker } from '../../../ui/AssetPicker';
/* ---------- MEDIA (Video / Gallery / Map / Slider) ---------- */
export const MediaStylePanel: React.FC<StylePanelProps> = ({ selectedId, nodeProps }) => {
const { actions } = useEditor();
const { setProp, setPropStyle } = useNodeProp(selectedId);
const style = nodeProps.style || {};
return (
<>
{/* Video URL */}
{nodeProps.videoUrl !== undefined && (
<div style={sectionGap}>
<label style={labelStyle}>Video URL</label>
<input type="text" value={nodeProps.videoUrl || ''} onChange={(e) => setProp('videoUrl', e.target.value)} placeholder="YouTube, Vimeo, or .mp4 URL" style={inputStyle} />
</div>
)}
{/* Map URL */}
{nodeProps.embedUrl !== undefined && nodeProps.videoUrl === undefined && (
<div style={sectionGap}>
<label style={labelStyle}>Embed URL</label>
<input type="text" value={nodeProps.embedUrl || ''} onChange={(e) => setProp('embedUrl', e.target.value)} placeholder="Google Maps embed URL" style={inputStyle} />
</div>
)}
{/* Map address */}
{nodeProps.address !== undefined && (
<div style={sectionGap}>
<label style={labelStyle}>Address</label>
<input type="text" value={nodeProps.address || ''} onChange={(e) => setProp('address', e.target.value)} placeholder="123 Main St..." style={inputStyle} />
</div>
)}
{/* Video options */}
{nodeProps.autoplay !== undefined && (
<div style={sectionGap}>
<label style={{ ...labelStyle, display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
<input type="checkbox" checked={nodeProps.autoplay || false} onChange={(e) => setProp('autoplay', e.target.checked)} />
Autoplay
</label>
</div>
)}
{nodeProps.loop !== undefined && (
<div style={sectionGap}>
<label style={{ ...labelStyle, display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
<input type="checkbox" checked={nodeProps.loop || false} onChange={(e) => setProp('loop', e.target.checked)} />
Loop
</label>
</div>
)}
{nodeProps.controls !== undefined && (
<div style={sectionGap}>
<label style={{ ...labelStyle, display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
<input type="checkbox" checked={nodeProps.controls !== false} onChange={(e) => setProp('controls', e.target.checked)} />
Show Controls
</label>
</div>
)}
{/* Gallery items */}
{nodeProps.images !== undefined && Array.isArray(nodeProps.images) && (
<CollapsibleSection title="Images">
<ArrayPropEditor
selectedId={selectedId}
propKey="images"
items={nodeProps.images}
renderItem={(item: any, index: number) => (
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{item.src !== undefined && (
<AssetPicker
variant="compact"
value={item.src || ''}
onChange={(url) => {
actions.setProp(selectedId, (props: any) => {
const updated = [...(props.images || [])];
updated[index] = { ...updated[index], src: url };
props.images = updated;
});
}}
/>
)}
{item.caption !== undefined && (
<input type="text" value={item.caption || ''} onChange={(e) => {
actions.setProp(selectedId, (props: any) => {
const updated = [...(props.images || [])];
updated[index] = { ...updated[index], caption: e.target.value };
props.images = updated;
});
}} placeholder="Caption" style={smallInputStyle} />
)}
</div>
)}
emptyItem={{ src: '', caption: '' }}
/>
</CollapsibleSection>
)}
{/* Slides */}
{nodeProps.slides !== undefined && Array.isArray(nodeProps.slides) && (
<CollapsibleSection title="Slides">
<ArrayPropEditor
selectedId={selectedId}
propKey="slides"
items={nodeProps.slides}
renderItem={(item: any, index: number) => (
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{item.heading !== undefined && (
<input type="text" value={item.heading || ''} onChange={(e) => {
actions.setProp(selectedId, (props: any) => {
const updated = [...(props.slides || [])];
updated[index] = { ...updated[index], heading: e.target.value };
props.slides = updated;
});
}} placeholder="Heading" style={smallInputStyle} />
)}
{item.text !== undefined && (
<textarea value={item.text || ''} onChange={(e) => {
actions.setProp(selectedId, (props: any) => {
const updated = [...(props.slides || [])];
updated[index] = { ...updated[index], text: e.target.value };
props.slides = updated;
});
}} placeholder="Text" rows={2} style={{ ...smallInputStyle, resize: 'vertical' }} />
)}
{item.image !== undefined && (
<AssetPicker
variant="compact"
value={item.image || ''}
onChange={(url) => {
actions.setProp(selectedId, (props: any) => {
const updated = [...(props.slides || [])];
updated[index] = { ...updated[index], image: url };
props.slides = updated;
});
}}
/>
)}
</div>
)}
emptyItem={{ heading: 'New Slide', text: '', image: '' }}
/>
</CollapsibleSection>
)}
{/* Overlay */}
{nodeProps.overlayColor !== undefined && (
<CollapsibleSection title="Overlay" defaultOpen={false}>
<ColorPickerField label="Color" value={nodeProps.overlayColor || '#000000'} onChange={(v) => setProp('overlayColor', v)} />
{nodeProps.overlayOpacity !== undefined && (
<div style={sectionGap}>
<label style={labelStyle}>Opacity: {nodeProps.overlayOpacity ?? 0}%</label>
<input type="range" min={0} max={100} value={nodeProps.overlayOpacity ?? 0} onChange={(e) => setProp('overlayOpacity', parseInt(e.target.value))} style={{ width: '100%' }} />
</div>
)}
</CollapsibleSection>
)}
{/* Background & padding */}
<CollapsibleSection title="Style" defaultOpen={false}>
<div className="guided-section">
<SectionLabel>Background</SectionLabel>
<ColorSwatchGrid colors={BG_COLORS} activeValue={style.backgroundColor} onSelect={(v: string) => setPropStyle('backgroundColor', v)} />
</div>
<div className="guided-section">
<SectionLabel>Padding</SectionLabel>
<PresetButtonGrid presets={SPACING_PRESETS} activeValue={style.padding as string} onSelect={(v) => setPropStyle('padding', v)} />
</div>
<div className="guided-section">
<SectionLabel>Border Radius</SectionLabel>
<PresetButtonGrid presets={RADIUS_PRESETS} activeValue={style.borderRadius as string} onSelect={(v) => setPropStyle('borderRadius', v)} />
</div>
</CollapsibleSection>
</>
);
};