import { useState } from 'react'
import { ThemeConfig, ThemeVariant } from '@/types/project'
import { Card } from '@/components/ui/card'
import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { Slider } from '@/components/ui/slider'
import { Button } from '@/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { PaintBrush, Sparkle, Plus, Trash, Moon, Sun, Palette } from '@phosphor-icons/react'
import { AIService } from '@/lib/ai-service'
import { toast } from 'sonner'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog'
interface StyleDesignerProps {
theme: ThemeConfig
onThemeChange: (theme: ThemeConfig | ((current: ThemeConfig) => ThemeConfig)) => void
}
export function StyleDesigner({ theme, onThemeChange }: StyleDesignerProps) {
const [newColorName, setNewColorName] = useState('')
const [newColorValue, setNewColorValue] = useState('#000000')
const [customColorDialogOpen, setCustomColorDialogOpen] = useState(false)
const [newVariantDialogOpen, setNewVariantDialogOpen] = useState(false)
const [newVariantName, setNewVariantName] = useState('')
if (!theme.variants || theme.variants.length === 0) {
return (
Theme configuration is invalid or missing
)
}
const activeVariant = theme.variants.find((v) => v.id === theme.activeVariantId) || theme.variants[0]
const updateTheme = (updates: Partial) => {
onThemeChange((current) => ({ ...current, ...updates }))
}
const updateActiveVariantColors = (colorUpdates: Partial) => {
onThemeChange((current) => ({
...current,
variants: (current.variants || []).map((v) =>
v.id === current.activeVariantId
? { ...v, colors: { ...v.colors, ...colorUpdates } }
: v
),
}))
}
const addCustomColor = () => {
if (!newColorName.trim()) {
toast.error('Please enter a color name')
return
}
updateActiveVariantColors({
customColors: {
...activeVariant.colors.customColors,
[newColorName]: newColorValue,
},
})
setNewColorName('')
setNewColorValue('#000000')
setCustomColorDialogOpen(false)
toast.success(`Added custom color: ${newColorName}`)
}
const removeCustomColor = (colorName: string) => {
const { [colorName]: _, ...remainingColors } = activeVariant.colors.customColors
updateActiveVariantColors({
customColors: remainingColors,
})
toast.success(`Removed custom color: ${colorName}`)
}
const addVariant = () => {
if (!newVariantName.trim()) {
toast.error('Please enter a variant name')
return
}
const newVariant: ThemeVariant = {
id: `variant-${Date.now()}`,
name: newVariantName,
colors: { ...activeVariant.colors, customColors: {} },
}
onThemeChange((current) => ({
...current,
variants: [...(current.variants || []), newVariant],
activeVariantId: newVariant.id,
}))
setNewVariantName('')
setNewVariantDialogOpen(false)
toast.success(`Added theme variant: ${newVariantName}`)
}
const deleteVariant = (variantId: string) => {
if (!theme.variants || theme.variants.length <= 1) {
toast.error('Cannot delete the last theme variant')
return
}
onThemeChange((current) => {
const remainingVariants = (current.variants || []).filter((v) => v.id !== variantId)
return {
...current,
variants: remainingVariants,
activeVariantId: current.activeVariantId === variantId ? remainingVariants[0].id : current.activeVariantId,
}
})
toast.success('Theme variant deleted')
}
const duplicateVariant = (variantId: string) => {
const variantToDuplicate = (theme.variants || []).find((v) => v.id === variantId)
if (!variantToDuplicate) return
const newVariant: ThemeVariant = {
id: `variant-${Date.now()}`,
name: `${variantToDuplicate.name} Copy`,
colors: { ...variantToDuplicate.colors, customColors: { ...variantToDuplicate.colors.customColors } },
}
onThemeChange((current) => ({
...current,
variants: [...(current.variants || []), newVariant],
}))
toast.success('Theme variant duplicated')
}
const generateThemeWithAI = async () => {
const description = prompt('Describe the visual style you want (e.g., "modern and professional", "vibrant and playful"):')
if (!description) return
try {
toast.info('Generating theme with AI...')
const generatedTheme = await AIService.generateThemeFromDescription(description)
if (generatedTheme) {
onThemeChange((current) => ({ ...current, ...generatedTheme }))
toast.success('Theme generated successfully!')
} else {
toast.error('AI generation failed. Please try again.')
}
} catch (error) {
toast.error('Failed to generate theme')
console.error(error)
}
}
const renderColorInput = (label: string, colorKey: keyof typeof activeVariant.colors, excludeCustom = true) => {
if (excludeCustom && colorKey === 'customColors') return null
const value = activeVariant.colors[colorKey] as string
return (
)
}
return (
Theme Designer
Create and customize multiple theme variants with custom colors
Theme Variants
{theme.variants.map((variant) => (
))}
Standard Colors
Extended Colors
Custom Colors ({Object.keys(activeVariant.colors.customColors).length})
{renderColorInput('Primary Color', 'primaryColor')}
{renderColorInput('Secondary Color', 'secondaryColor')}
{renderColorInput('Error Color', 'errorColor')}
{renderColorInput('Warning Color', 'warningColor')}
{renderColorInput('Success Color', 'successColor')}
{renderColorInput('Background', 'background')}
{renderColorInput('Surface', 'surface')}
{renderColorInput('Text', 'text')}
{renderColorInput('Text Secondary', 'textSecondary')}
{renderColorInput('Border', 'border')}
Add custom colors for your specific needs
{Object.keys(activeVariant.colors.customColors).length === 0 ? (
No custom colors yet
) : (
{Object.entries(activeVariant.colors.customColors).map(([name, value]) => (
))}
)}
Typography
updateTheme({ fontFamily: e.target.value })}
placeholder="Roboto, Arial, sans-serif"
/>
{theme.fontSize.small}px
updateTheme({
fontSize: { ...theme.fontSize, small: value },
})
}
min={10}
max={20}
step={1}
/>
{theme.fontSize.medium}px
updateTheme({
fontSize: { ...theme.fontSize, medium: value },
})
}
min={12}
max={24}
step={1}
/>
{theme.fontSize.large}px
updateTheme({
fontSize: { ...theme.fontSize, large: value },
})
}
min={16}
max={48}
step={1}
/>
Spacing & Shape
{theme.spacing}px
updateTheme({ spacing: value })}
min={4}
max={16}
step={1}
/>
Material UI multiplies this value (e.g., spacing(2) = {theme.spacing * 2}px)
{theme.borderRadius}px
updateTheme({ borderRadius: value })
}
min={0}
max={24}
step={1}
/>
Preview - {activeVariant.name} Mode
Primary
Secondary
Error
Warning
Success
{Object.entries(activeVariant.colors.customColors).map(([name, color]) => (
{name}
))}
Large Text Sample
Medium Text Sample
Small Text Sample (Secondary)
)
}