reaminig 5-t-9
This commit is contained in:
296
frontend/src/components/sites/StyleEditor.tsx
Normal file
296
frontend/src/components/sites/StyleEditor.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* 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 'lucide-react';
|
||||
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 */ .custom-class { color: #333; }"
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user