Files
igny8/frontend/src/components/sites/StyleEditor.tsx
IGNY8 VPS (Salman) e96069775c GLobal Styling part 1
2026-01-01 14:54:27 +00:00

297 lines
10 KiB
TypeScript

/**
* Style Editor
* Phase 7: Layout & Template System
* Component for editing CSS styles and theme customization
*/
import React, { useState } from 'react';
import { CodeIcon, PaletteIcon, TypeIcon, SaveIcon, RefreshCwIcon } from '../../icons';
import { Card } from '../../ui/card';
import Button from '../../ui/button/Button';
import Label from '../../form/Label';
import TextArea from '../../form/input/TextArea';
import { useToast } from '../../ui/toast/ToastContainer';
export interface StyleSettings {
customCSS?: string;
colorPalette?: {
primary: string;
secondary: string;
accent: string;
background: string;
text: string;
};
typography?: {
fontFamily: string;
headingFont: string;
fontSize: string;
lineHeight: string;
};
spacing?: {
base: string;
scale: string;
};
}
interface StyleEditorProps {
styleSettings: StyleSettings;
onChange: (settings: StyleSettings) => void;
onSave?: () => void;
onReset?: () => void;
}
export default function StyleEditor({ styleSettings, onChange, onSave, onReset }: StyleEditorProps) {
const toast = useToast();
const [activeTab, setActiveTab] = useState<'css' | 'colors' | 'typography' | 'spacing'>('css');
const [customCSS, setCustomCSS] = useState(styleSettings.customCSS || '');
const updateSettings = (updates: Partial<StyleSettings>) => {
onChange({ ...styleSettings, ...updates });
};
const handleCSSChange = (value: string) => {
setCustomCSS(value);
updateSettings({ customCSS: value });
};
const handleColorChange = (key: string, value: string) => {
updateSettings({
colorPalette: {
...styleSettings.colorPalette,
[key]: value,
} as StyleSettings['colorPalette'],
});
};
const handleTypographyChange = (key: string, value: string) => {
updateSettings({
typography: {
...styleSettings.typography,
[key]: value,
} as StyleSettings['typography'],
});
};
const handleSpacingChange = (key: string, value: string) => {
updateSettings({
spacing: {
...styleSettings.spacing,
[key]: value,
} as StyleSettings['spacing'],
});
};
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h2 className="text-xl font-bold text-gray-900 dark:text-white">Style Editor</h2>
<p className="text-sm text-gray-600 dark:text-gray-400">Customize CSS, colors, typography, and spacing</p>
</div>
<div className="flex gap-2">
{onReset && (
<Button variant="outline" onClick={onReset}>
<RefreshCwIcon className="w-4 h-4 mr-2" />
Reset
</Button>
)}
{onSave && (
<Button variant="primary" onClick={onSave}>
<SaveIcon className="w-4 h-4 mr-2" />
Save Styles
</Button>
)}
</div>
</div>
{/* Tabs */}
<div className="border-b border-gray-200 dark:border-gray-700">
<div className="flex gap-4">
<button
type="button"
onClick={() => setActiveTab('css')}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'css'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
<CodeIcon className="w-4 h-4 inline mr-2" />
Custom CSS
</button>
<button
type="button"
onClick={() => setActiveTab('colors')}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'colors'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
<PaletteIcon className="w-4 h-4 inline mr-2" />
Colors
</button>
<button
type="button"
onClick={() => setActiveTab('typography')}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'typography'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
<TypeIcon className="w-4 h-4 inline mr-2" />
Typography
</button>
<button
type="button"
onClick={() => setActiveTab('spacing')}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
activeTab === 'spacing'
? 'border-brand-500 text-brand-600 dark:text-brand-400'
: 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
Spacing
</button>
</div>
</div>
{/* Custom CSS Tab */}
{activeTab === 'css' && (
<Card className="p-6">
<div className="space-y-4">
<div>
<Label>Custom CSS</Label>
<TextArea
value={customCSS}
onChange={handleCSSChange}
rows={20}
placeholder="/* Add your custom CSS here */&#10;.custom-class {&#10; color: #333;&#10;}"
className="mt-1 font-mono text-sm"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Add custom CSS rules. These will be applied to your site.
</p>
</div>
</div>
</Card>
)}
{/* Colors Tab */}
{activeTab === 'colors' && (
<Card className="p-6">
<div className="space-y-4">
{['primary', 'secondary', 'accent', 'background', 'text'].map((colorKey) => (
<div key={colorKey}>
<Label>{colorKey.charAt(0).toUpperCase() + colorKey.slice(1)} Color</Label>
<div className="flex gap-2 mt-1">
<input
type="color"
value={styleSettings.colorPalette?.[colorKey as keyof typeof styleSettings.colorPalette] || '#000000'}
onChange={(e) => handleColorChange(colorKey, e.target.value)}
className="w-16 h-10 rounded border border-gray-300 dark:border-gray-700 cursor-pointer"
/>
<input
type="text"
value={styleSettings.colorPalette?.[colorKey as keyof typeof styleSettings.colorPalette] || ''}
onChange={(e) => handleColorChange(colorKey, e.target.value)}
placeholder="#000000"
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
</div>
))}
</div>
</Card>
)}
{/* Typography Tab */}
{activeTab === 'typography' && (
<Card className="p-6">
<div className="space-y-4">
<div>
<Label>Font Family</Label>
<input
type="text"
value={styleSettings.typography?.fontFamily || ''}
onChange={(e) => handleTypographyChange('fontFamily', e.target.value)}
placeholder="Arial, sans-serif"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Heading Font</Label>
<input
type="text"
value={styleSettings.typography?.headingFont || ''}
onChange={(e) => handleTypographyChange('headingFont', e.target.value)}
placeholder="Georgia, serif"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Base Font Size</Label>
<input
type="text"
value={styleSettings.typography?.fontSize || ''}
onChange={(e) => handleTypographyChange('fontSize', e.target.value)}
placeholder="16px"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
<div>
<Label>Line Height</Label>
<input
type="text"
value={styleSettings.typography?.lineHeight || ''}
onChange={(e) => handleTypographyChange('lineHeight', e.target.value)}
placeholder="1.5"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
</div>
</div>
</Card>
)}
{/* Spacing Tab */}
{activeTab === 'spacing' && (
<Card className="p-6">
<div className="space-y-4">
<div>
<Label>Base Spacing Unit</Label>
<input
type="text"
value={styleSettings.spacing?.base || ''}
onChange={(e) => handleSpacingChange('base', e.target.value)}
placeholder="8px"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Base unit for spacing calculations (e.g., 8px, 1rem)
</p>
</div>
<div>
<Label>Spacing Scale</Label>
<input
type="text"
value={styleSettings.spacing?.scale || ''}
onChange={(e) => handleSpacingChange('scale', e.target.value)}
placeholder="1.5"
className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Multiplier for spacing scale (e.g., 1.5 for 1.5x spacing)
</p>
</div>
</div>
</Card>
)}
</div>
);
}