98 lines
4.2 KiB
TypeScript
98 lines
4.2 KiB
TypeScript
|
|
import React, { useCallback, useRef } from 'react';
|
||
|
|
import { useEditor } from '@craftjs/core';
|
||
|
|
import {
|
||
|
|
BG_COLORS,
|
||
|
|
SPACING_PRESETS,
|
||
|
|
} from '../../../constants/presets';
|
||
|
|
import {
|
||
|
|
StylePanelProps,
|
||
|
|
SectionLabel,
|
||
|
|
ColorSwatchGrid,
|
||
|
|
GradientSwatchGrid,
|
||
|
|
PresetButtonGrid,
|
||
|
|
CollapsibleSection,
|
||
|
|
ColorPickerField,
|
||
|
|
uploadToWhp,
|
||
|
|
labelStyle,
|
||
|
|
inputStyle,
|
||
|
|
smallInputStyle,
|
||
|
|
btnActiveStyle,
|
||
|
|
sectionGap,
|
||
|
|
} from './shared';
|
||
|
|
|
||
|
|
/* ---------- BACKGROUND SECTION ---------- */
|
||
|
|
export const BackgroundSectionStylePanel: React.FC<StylePanelProps> = ({ selectedId, nodeProps }) => {
|
||
|
|
const { actions } = useEditor();
|
||
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||
|
|
|
||
|
|
const setProp = useCallback((key: string, value: any) => {
|
||
|
|
actions.setProp(selectedId, (props: any) => { props[key] = value; });
|
||
|
|
}, [actions, selectedId]);
|
||
|
|
|
||
|
|
const setPropStyle = useCallback((key: string, value: string) => {
|
||
|
|
actions.setProp(selectedId, (props: any) => {
|
||
|
|
props.style = { ...props.style, [key]: value };
|
||
|
|
});
|
||
|
|
}, [actions, selectedId]);
|
||
|
|
|
||
|
|
const handleUpload = useCallback(async (file: File) => {
|
||
|
|
const url = await uploadToWhp(file);
|
||
|
|
if (url) setProp('bgImage', url);
|
||
|
|
}, [setProp]);
|
||
|
|
|
||
|
|
const style = nodeProps.style || {};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
{/* Background Image */}
|
||
|
|
<CollapsibleSection title="Background Image">
|
||
|
|
{nodeProps.bgImage && (
|
||
|
|
<div style={{ marginBottom: 6, borderRadius: 4, overflow: 'hidden', border: '1px solid #3f3f46', position: 'relative' }}>
|
||
|
|
<img src={nodeProps.bgImage} alt="" style={{ width: '100%', height: 80, objectFit: 'cover', display: 'block' }} />
|
||
|
|
<button onClick={() => setProp('bgImage', '')} style={{ position: 'absolute', top: 2, right: 2, width: 20, height: 20, borderRadius: '50%', background: 'rgba(0,0,0,0.7)', border: 'none', color: '#fff', cursor: 'pointer', fontSize: 10, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||
|
|
<i className="fa fa-times" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
<div style={{ display: 'flex', gap: 4 }}>
|
||
|
|
<button onClick={() => fileInputRef.current?.click()} style={{ ...btnActiveStyle(false), flex: 1 }}>
|
||
|
|
<i className="fa fa-upload" style={{ marginRight: 4 }} /> Upload
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<input ref={fileInputRef} type="file" accept="image/*" style={{ display: 'none' }}
|
||
|
|
onChange={(e) => { const f = e.target.files?.[0]; if (f) handleUpload(f); e.target.value = ''; }} />
|
||
|
|
<input type="text" value={nodeProps.bgImage || ''} placeholder="Or paste image URL..."
|
||
|
|
onChange={(e) => setProp('bgImage', e.target.value)} style={{ ...smallInputStyle, width: '100%', marginTop: 4 }} />
|
||
|
|
</CollapsibleSection>
|
||
|
|
|
||
|
|
{/* Background Color */}
|
||
|
|
<CollapsibleSection title="Background Color">
|
||
|
|
<ColorPickerField label="Background" value={nodeProps.bgColor || '#1e293b'} onChange={(v) => setProp('bgColor', v)} />
|
||
|
|
</CollapsibleSection>
|
||
|
|
|
||
|
|
{/* Overlay */}
|
||
|
|
<CollapsibleSection title="Overlay">
|
||
|
|
<ColorPickerField label="Overlay Color" value={nodeProps.overlayColor || '#000000'} onChange={(v) => setProp('overlayColor', v)} />
|
||
|
|
<div style={sectionGap}>
|
||
|
|
<label style={labelStyle}>Opacity: {Math.round((nodeProps.overlayOpacity ?? 0.4) * 100)}%</label>
|
||
|
|
<input type="range" min={0} max={100} value={Math.round((nodeProps.overlayOpacity ?? 0.4) * 100)}
|
||
|
|
onChange={(e) => setProp('overlayOpacity', parseInt(e.target.value) / 100)} style={{ width: '100%' }} />
|
||
|
|
</div>
|
||
|
|
</CollapsibleSection>
|
||
|
|
|
||
|
|
{/* Layout */}
|
||
|
|
<CollapsibleSection title="Layout" defaultOpen={false}>
|
||
|
|
<div style={sectionGap}>
|
||
|
|
<label style={labelStyle}>Inner Max Width</label>
|
||
|
|
<input type="text" value={nodeProps.innerMaxWidth || '1200px'}
|
||
|
|
onChange={(e) => setProp('innerMaxWidth', e.target.value)} style={inputStyle} />
|
||
|
|
</div>
|
||
|
|
<div className="guided-section">
|
||
|
|
<SectionLabel>Padding</SectionLabel>
|
||
|
|
<PresetButtonGrid presets={SPACING_PRESETS} activeValue={style.padding as string} onSelect={(v) => setPropStyle('padding', v)} />
|
||
|
|
</div>
|
||
|
|
</CollapsibleSection>
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
};
|