feat: add brand strip customization to BackgroundPanel

- Implemented a new section in BackgroundPanel for configuring a brand strip.
- Added options for enabling/disabling the brand strip, setting its position, height, color, text, text color, font family, and font size.
- Integrated FONT_FAMILIES for font selection in the brand strip.
- Updated canvasStore to include default values for the brand strip.
- Enhanced TextInspector and CodeInspector to allow font loading and selection with previews.
- Created a FontLoader component to manage font loading from Google Fonts.
- Added LayersPanel for managing canvas elements with improved UI and functionality.
- Introduced fontLoader utility to handle dynamic font loading.
This commit is contained in:
2026-01-07 17:07:55 +02:00
parent 6510dac3bc
commit 84b7a6a80b
12 changed files with 1044 additions and 162 deletions
+141 -33
View File
@@ -17,28 +17,57 @@ const ArrowInspector: React.FC<ArrowInspectorProps> = ({ element }) => {
update({ props: { ...element.props, ...props } });
};
const addControlPoint = () => {
const start = element.points[0];
const end = element.points[element.points.length - 1];
const currentControlPoints = element.props.controlPoints || [];
if (currentControlPoints.length < 2) {
const midX = (start.x + end.x) / 2;
const midY = (start.y + end.y) / 2;
const dx = end.x - start.x;
const dy = end.y - start.y;
const newPoint = currentControlPoints.length === 0
? { x: midX - dy * 0.3, y: midY + dx * 0.3 }
: { x: midX + dy * 0.3, y: midY - dx * 0.3 };
updateProps({ controlPoints: [...currentControlPoints, newPoint] });
}
};
const removeControlPoint = (index: number) => {
const currentControlPoints = element.props.controlPoints || [];
const newControlPoints = currentControlPoints.filter((_, i) => i !== index);
updateProps({ controlPoints: newControlPoints });
};
const resetControlPoints = () => {
updateProps({ controlPoints: [] });
};
return (
<div className="space-y-4">
{/* Style */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Style</label>
<div className="flex gap-2">
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Style</label>
<div className="flex gap-2 p-1 bg-white/5 rounded-lg border border-white/5">
<button
onClick={() => updateProps({ style: 'straight' })}
className={`flex-1 py-2 rounded text-sm ${
className={`flex-1 py-2 rounded-md text-sm font-medium transition-all ${
element.props.style === 'straight'
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
Straight
</button>
<button
onClick={() => updateProps({ style: 'curved' })}
className={`flex-1 py-2 rounded text-sm ${
className={`flex-1 py-2 rounded-md text-sm font-medium transition-all ${
element.props.style === 'curved'
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
Curved
@@ -46,35 +75,86 @@ const ArrowInspector: React.FC<ArrowInspectorProps> = ({ element }) => {
</div>
</div>
{/* Control points for curved arrows */}
{element.props.style === 'curved' && (
<div>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">
Control Points ({(element.props.controlPoints || []).length}/2)
</label>
<div className="space-y-2">
<div className="flex gap-2">
<button
onClick={addControlPoint}
disabled={(element.props.controlPoints || []).length >= 2}
className="flex-1 py-2 px-3 rounded-lg text-xs font-medium bg-blue-600/20 text-blue-400 hover:bg-blue-600/30 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
+ Add Point
</button>
<button
onClick={resetControlPoints}
disabled={(element.props.controlPoints || []).length === 0}
className="py-2 px-3 rounded-lg text-xs font-medium bg-white/5 text-neutral-400 hover:bg-white/10 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Reset
</button>
</div>
{(element.props.controlPoints || []).length > 0 && (
<div className="space-y-1">
{(element.props.controlPoints || []).map((cp, index) => (
<div key={index} className="flex items-center justify-between py-1.5 px-2 bg-white/5 rounded-md">
<span className="text-xs text-neutral-400">
Point {index + 1}: ({Math.round(cp.x)}, {Math.round(cp.y)})
</span>
<button
onClick={() => removeControlPoint(index)}
className="p-1 rounded hover:bg-red-500/20 text-neutral-500 hover:text-red-400 transition-colors"
>
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
))}
</div>
)}
<p className="text-xs text-neutral-500">
Drag the blue handles on the canvas to adjust the curve shape.
</p>
</div>
</div>
)}
{/* Color */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Color</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={element.props.color}
onChange={(e) => updateProps({ color: e.target.value })}
className="w-10 h-10 rounded cursor-pointer bg-transparent"
/>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Color</label>
<div className="flex gap-2 items-center p-2 bg-white/5 rounded-lg border border-white/5">
<div className="w-8 h-8 rounded overflow-hidden relative border border-white/10 shrink-0">
<input
type="color"
value={element.props.color}
onChange={(e) => updateProps({ color: e.target.value })}
className="absolute inset-[-4px] w-[200%] h-[200%] cursor-pointer p-0 m-0 border-none"
/>
</div>
<input
type="text"
value={element.props.color}
onChange={(e) => updateProps({ color: e.target.value })}
className="flex-1 bg-neutral-700 text-white px-3 py-2 rounded text-sm"
className="flex-1 bg-transparent text-white text-sm focus:outline-none font-mono"
/>
</div>
</div>
{/* Thickness */}
<div>
<label className="block text-sm text-neutral-400 mb-2">
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">
Thickness: {element.props.thickness}px
</label>
<input
type="range"
value={element.props.thickness}
onChange={(e) => updateProps({ thickness: parseInt(e.target.value) })}
className="w-full"
className="w-full h-1.5 bg-white/10 rounded-lg appearance-none cursor-pointer accent-blue-600"
min={1}
max={12}
/>
@@ -82,34 +162,34 @@ const ArrowInspector: React.FC<ArrowInspectorProps> = ({ element }) => {
{/* Arrow head */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Arrow Head</label>
<div className="flex gap-2">
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Arrow Head</label>
<div className="flex gap-2 p-1 bg-white/5 rounded-lg border border-white/5">
<button
onClick={() => updateProps({ head: 'filled' })}
className={`flex-1 py-2 rounded text-sm ${
className={`flex-1 py-2 rounded-md text-xs font-medium transition-all ${
element.props.head === 'filled'
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
Filled
</button>
<button
onClick={() => updateProps({ head: 'outline' })}
className={`flex-1 py-2 rounded text-sm ${
className={`flex-1 py-2 rounded-md text-xs font-medium transition-all ${
element.props.head === 'outline'
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
Outline
</button>
<button
onClick={() => updateProps({ head: 'none' })}
className={`flex-1 py-2 rounded text-sm ${
className={`flex-1 py-2 rounded-md text-xs font-medium transition-all ${
element.props.head === 'none'
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
None
@@ -117,11 +197,39 @@ const ArrowInspector: React.FC<ArrowInspectorProps> = ({ element }) => {
</div>
</div>
{/* Points info */}
{/* Label */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Points</label>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Label (Optional)</label>
<input
type="text"
value={element.props.label || ''}
onChange={(e) => updateProps({ label: e.target.value || undefined })}
placeholder="Add a label..."
className="w-full bg-white/5 text-white px-3 py-2 rounded-lg text-sm border border-white/5 focus:border-blue-500/50 focus:outline-none"
/>
</div>
{/* Label position */}
{element.props.label && (
<div>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">
Label Position: {Math.round((element.props.labelPosition || 0.5) * 100)}%
</label>
<input
type="range"
value={(element.props.labelPosition || 0.5) * 100}
onChange={(e) => updateProps({ labelPosition: parseInt(e.target.value) / 100 })}
className="w-full h-1.5 bg-white/10 rounded-lg appearance-none cursor-pointer accent-blue-600"
min={0}
max={100}
/>
</div>
)}
{/* Points info */}
<div className="pt-2 border-t border-white/5">
<p className="text-xs text-neutral-500">
Drag the blue handles on the canvas to adjust arrow points.
Drag the white handles on the canvas to move the arrow endpoints.
</p>
</div>
</div>
@@ -1,5 +1,6 @@
import React from 'react';
import { useCanvasStore } from '../../store/canvasStore';
import { FONT_FAMILIES } from '../../types';
const GRADIENT_PRESETS = [
{ from: '#101022', to: '#1f1f3a', name: 'Midnight' },
@@ -152,6 +153,178 @@ const BackgroundPanel: React.FC = () => {
</div>
</>
)}
{/* Brand Strip Section */}
<div className="pt-4 border-t border-white/5">
<div className="flex items-center justify-between mb-3">
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider">Brand Strip</label>
<button
onClick={() => setBackground({
brandStrip: { ...background.brandStrip, enabled: !background.brandStrip?.enabled }
})}
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${
background.brandStrip?.enabled ? 'bg-blue-600' : 'bg-white/10'
}`}
>
<span
className={`inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform ${
background.brandStrip?.enabled ? 'translate-x-4' : 'translate-x-1'
}`}
/>
</button>
</div>
{background.brandStrip?.enabled && (
<div className="space-y-4">
{/* Position */}
<div>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Position</label>
<div className="flex gap-2 p-1 bg-white/5 rounded-lg border border-white/5">
<button
onClick={() => setBackground({
brandStrip: { ...background.brandStrip, position: 'top' }
})}
className={`flex-1 py-1.5 rounded-md text-xs font-medium transition-all ${
background.brandStrip.position === 'top'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
Top
</button>
<button
onClick={() => setBackground({
brandStrip: { ...background.brandStrip, position: 'bottom' }
})}
className={`flex-1 py-1.5 rounded-md text-xs font-medium transition-all ${
background.brandStrip.position === 'bottom'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
Bottom
</button>
</div>
</div>
{/* Height */}
<div>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">
Height: {background.brandStrip.height || 60}px
</label>
<input
type="range"
min="30"
max="120"
value={background.brandStrip.height || 60}
onChange={(e) => setBackground({
brandStrip: { ...background.brandStrip, height: parseInt(e.target.value) }
})}
className="w-full h-1.5 bg-white/10 rounded-lg appearance-none cursor-pointer accent-blue-600"
/>
</div>
{/* Strip Color */}
<div>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Strip Color</label>
<div className="flex gap-2 items-center p-2 bg-white/5 rounded-lg border border-white/5">
<div className="w-6 h-6 rounded overflow-hidden relative border border-white/10 shrink-0">
<input
type="color"
value={background.brandStrip.color || '#000000'}
onChange={(e) => setBackground({
brandStrip: { ...background.brandStrip, color: e.target.value }
})}
className="absolute inset-[-4px] w-[200%] h-[200%] cursor-pointer"
/>
</div>
<input
type="text"
value={background.brandStrip.color || '#000000'}
onChange={(e) => setBackground({
brandStrip: { ...background.brandStrip, color: e.target.value }
})}
className="w-full bg-transparent text-white text-xs focus:outline-none font-mono"
/>
</div>
</div>
{/* Brand Text */}
<div>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Text</label>
<input
type="text"
value={background.brandStrip.text || ''}
onChange={(e) => setBackground({
brandStrip: { ...background.brandStrip, text: e.target.value }
})}
placeholder="@yourhandle"
className="w-full bg-white/5 text-white px-3 py-2 rounded-lg text-sm border border-white/5 focus:border-blue-500/50 focus:outline-none"
/>
</div>
{/* Text Color */}
<div>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Text Color</label>
<div className="flex gap-2 items-center p-2 bg-white/5 rounded-lg border border-white/5">
<div className="w-6 h-6 rounded overflow-hidden relative border border-white/10 shrink-0">
<input
type="color"
value={background.brandStrip.textColor || '#ffffff'}
onChange={(e) => setBackground({
brandStrip: { ...background.brandStrip, textColor: e.target.value }
})}
className="absolute inset-[-4px] w-[200%] h-[200%] cursor-pointer"
/>
</div>
<input
type="text"
value={background.brandStrip.textColor || '#ffffff'}
onChange={(e) => setBackground({
brandStrip: { ...background.brandStrip, textColor: e.target.value }
})}
className="w-full bg-transparent text-white text-xs focus:outline-none font-mono"
/>
</div>
</div>
{/* Font Family */}
<div>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Font</label>
<select
value={background.brandStrip.fontFamily || 'Inter'}
onChange={(e) => setBackground({
brandStrip: { ...background.brandStrip, fontFamily: e.target.value }
})}
className="w-full bg-white/5 text-white px-3 py-2 rounded-lg text-sm border border-white/5 focus:border-blue-500/50 focus:outline-none"
>
{FONT_FAMILIES.brand.map((font) => (
<option key={font} value={font} style={{ fontFamily: font }}>
{font}
</option>
))}
</select>
</div>
{/* Font Size */}
<div>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">
Font Size: {background.brandStrip.fontSize || 16}px
</label>
<input
type="range"
min="12"
max="32"
value={background.brandStrip.fontSize || 16}
onChange={(e) => setBackground({
brandStrip: { ...background.brandStrip, fontSize: parseInt(e.target.value) }
})}
className="w-full h-1.5 bg-white/10 rounded-lg appearance-none cursor-pointer accent-blue-600"
/>
</div>
</div>
)}
</div>
</div>
);
};
+23 -10
View File
@@ -3,6 +3,7 @@ import { useCanvasStore } from '../../store/canvasStore';
import type { CodeElement, LineHighlight } from '../../types';
import { LANGUAGES, FONT_FAMILIES, CODE_THEMES } from '../../types';
import { detectLanguage } from '../../utils/highlighter';
import { loadFont } from '../../utils/fontLoader';
interface CodeInspectorProps {
element: CodeElement;
@@ -116,18 +117,30 @@ const CodeInspector: React.FC<CodeInspectorProps> = ({ element }) => {
{/* Font */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Font</label>
<select
value={element.props.fontFamily}
onChange={(e) => updateProps({ fontFamily: e.target.value })}
className="w-full bg-neutral-700 text-white px-3 py-2 rounded text-sm"
>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Font Family</label>
<div className="space-y-1 max-h-36 overflow-y-auto p-1 bg-white/5 rounded-lg border border-white/5">
{FONT_FAMILIES.code.map((font) => (
<option key={font} value={font}>
{font}
</option>
<button
key={font}
onClick={() => {
loadFont(font);
updateProps({ fontFamily: font });
}}
className={`w-full flex items-center justify-between px-3 py-2 rounded-md text-sm text-left transition-all ${
element.props.fontFamily === font
? 'bg-blue-600/20 text-blue-400 border border-blue-500/30'
: 'text-neutral-300 hover:bg-white/5 border border-transparent'
}`}
>
<span style={{ fontFamily: font }}>{font}</span>
{element.props.fontFamily === font && (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)}
</button>
))}
</select>
</div>
</div>
{/* Font size & Line height */}
+90 -63
View File
@@ -2,6 +2,7 @@ import React from 'react';
import { useCanvasStore } from '../../store/canvasStore';
import type { TextElement } from '../../types';
import { FONT_FAMILIES } from '../../types';
import { loadFont } from '../../utils/fontLoader';
interface TextInspectorProps {
element: TextElement;
@@ -18,43 +19,59 @@ const TextInspector: React.FC<TextInspectorProps> = ({ element }) => {
update({ props: { ...element.props, ...props } });
};
const handleFontChange = (fontFamily: string) => {
loadFont(fontFamily);
updateProps({ fontFamily });
};
return (
<div className="space-y-4">
{/* Text */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Text</label>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Text</label>
<textarea
value={element.props.text}
onChange={(e) => updateProps({ text: e.target.value })}
onBlur={saveToHistory}
className="w-full h-24 bg-neutral-900 text-white text-sm p-3 rounded resize-y"
className="w-full h-24 bg-white/5 text-white text-sm p-3 rounded-lg resize-y border border-white/5 focus:border-blue-500/50 focus:outline-none"
/>
</div>
{/* Font */}
{/* Font Family with Preview */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Font</label>
<select
value={element.props.fontFamily}
onChange={(e) => updateProps({ fontFamily: e.target.value })}
className="w-full bg-neutral-700 text-white px-3 py-2 rounded text-sm"
>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Font Family</label>
<div className="space-y-1.5 max-h-48 overflow-y-auto p-1 bg-white/5 rounded-lg border border-white/5">
{FONT_FAMILIES.text.map((font) => (
<option key={font} value={font}>
{font}
</option>
<button
key={font}
onClick={() => handleFontChange(font)}
className={`w-full flex items-center justify-between px-3 py-2 rounded-md text-sm text-left transition-all ${
element.props.fontFamily === font
? 'bg-blue-600/20 text-blue-400 border border-blue-500/30'
: 'text-neutral-300 hover:bg-white/5 border border-transparent'
}`}
>
<span style={{ fontFamily: font }}>{font}</span>
{element.props.fontFamily === font && (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)}
</button>
))}
</select>
</div>
</div>
{/* Font size */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Size: {element.props.fontSize}</label>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">
Size: {element.props.fontSize}px
</label>
<input
type="range"
value={element.props.fontSize}
onChange={(e) => updateProps({ fontSize: parseInt(e.target.value) })}
className="w-full"
className="w-full h-1.5 bg-white/10 rounded-lg appearance-none cursor-pointer accent-blue-600"
min={12}
max={96}
/>
@@ -62,53 +79,55 @@ const TextInspector: React.FC<TextInspectorProps> = ({ element }) => {
{/* Color */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Color</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={element.props.color}
onChange={(e) => updateProps({ color: e.target.value })}
className="w-10 h-10 rounded cursor-pointer bg-transparent"
/>
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Color</label>
<div className="flex gap-2 items-center p-2 bg-white/5 rounded-lg border border-white/5">
<div className="w-8 h-8 rounded overflow-hidden relative border border-white/10 shrink-0">
<input
type="color"
value={element.props.color}
onChange={(e) => updateProps({ color: e.target.value })}
className="absolute inset-[-4px] w-[200%] h-[200%] cursor-pointer p-0 m-0 border-none"
/>
</div>
<input
type="text"
value={element.props.color}
onChange={(e) => updateProps({ color: e.target.value })}
className="flex-1 bg-neutral-700 text-white px-3 py-2 rounded text-sm"
className="flex-1 bg-transparent text-white text-sm focus:outline-none font-mono"
/>
</div>
</div>
{/* Style buttons */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Style</label>
<div className="flex gap-2">
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Style</label>
<div className="flex gap-2 p-1 bg-white/5 rounded-lg border border-white/5">
<button
onClick={() => updateProps({ bold: !element.props.bold })}
className={`flex-1 py-2 rounded text-sm font-bold ${
className={`flex-1 py-2 rounded-md text-sm font-bold transition-all ${
element.props.bold
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
B
</button>
<button
onClick={() => updateProps({ italic: !element.props.italic })}
className={`flex-1 py-2 rounded text-sm italic ${
className={`flex-1 py-2 rounded-md text-sm italic transition-all ${
element.props.italic
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
I
</button>
<button
onClick={() => updateProps({ underline: !element.props.underline })}
className={`flex-1 py-2 rounded text-sm underline ${
className={`flex-1 py-2 rounded-md text-sm underline transition-all ${
element.props.underline
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
U
@@ -118,37 +137,43 @@ const TextInspector: React.FC<TextInspectorProps> = ({ element }) => {
{/* Alignment */}
<div>
<label className="block text-sm text-neutral-400 mb-2">Alignment</label>
<div className="flex gap-2">
<label className="block text-xs font-medium text-neutral-500 uppercase tracking-wider mb-2">Alignment</label>
<div className="flex gap-2 p-1 bg-white/5 rounded-lg border border-white/5">
<button
onClick={() => updateProps({ align: 'left' })}
className={`flex-1 py-2 rounded text-sm ${
className={`flex-1 py-2 rounded-md text-sm transition-all ${
element.props.align === 'left'
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
Left
<svg className="w-4 h-4 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h10M4 18h14" />
</svg>
</button>
<button
onClick={() => updateProps({ align: 'center' })}
className={`flex-1 py-2 rounded text-sm ${
className={`flex-1 py-2 rounded-md text-sm transition-all ${
element.props.align === 'center'
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
Center
<svg className="w-4 h-4 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M7 12h10M5 18h14" />
</svg>
</button>
<button
onClick={() => updateProps({ align: 'right' })}
className={`flex-1 py-2 rounded text-sm ${
className={`flex-1 py-2 rounded-md text-sm transition-all ${
element.props.align === 'right'
? 'bg-blue-600 text-white'
: 'bg-neutral-700 text-neutral-300'
? 'bg-neutral-700 text-white shadow-sm'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
>
Right
<svg className="w-4 h-4 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M10 12h10M6 18h14" />
</svg>
</button>
</div>
</div>
@@ -156,37 +181,39 @@ const TextInspector: React.FC<TextInspectorProps> = ({ element }) => {
{/* Background */}
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-sm text-neutral-400">Background</label>
<label className="text-xs font-medium text-neutral-500 uppercase tracking-wider">Background</label>
<button
onClick={() => updateProps({
background: element.props.background
? null
: { color: 'rgba(0,0,0,0.5)' }
})}
className={`w-12 h-6 rounded-full transition-colors ${
element.props.background ? 'bg-blue-600' : 'bg-neutral-600'
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${
element.props.background ? 'bg-blue-600' : 'bg-white/10'
}`}
>
<div
className={`w-5 h-5 bg-white rounded-full transition-transform ${
element.props.background ? 'translate-x-6' : 'translate-x-0.5'
<span
className={`inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform ${
element.props.background ? 'translate-x-4' : 'translate-x-1'
}`}
/>
</button>
</div>
{element.props.background && (
<div className="flex gap-2 items-center">
<input
type="color"
value={element.props.background.color.substring(0, 7)}
onChange={(e) => updateProps({ background: { color: e.target.value } })}
className="w-10 h-10 rounded cursor-pointer bg-transparent"
/>
<div className="flex gap-2 items-center p-2 bg-white/5 rounded-lg border border-white/5">
<div className="w-6 h-6 rounded overflow-hidden relative border border-white/10 shrink-0">
<input
type="color"
value={element.props.background.color.substring(0, 7)}
onChange={(e) => updateProps({ background: { color: e.target.value } })}
className="absolute inset-[-4px] w-[200%] h-[200%] cursor-pointer"
/>
</div>
<input
type="text"
value={element.props.background.color}
onChange={(e) => updateProps({ background: { color: e.target.value } })}
className="flex-1 bg-neutral-700 text-white px-3 py-2 rounded text-sm"
className="flex-1 bg-transparent text-white text-xs focus:outline-none font-mono"
/>
</div>
)}