feat: Add edit functionality for custom themes

- Add Sliders icon edit button to custom theme cards in PresetGallery
- Edit button positioned between Apply and Delete buttons
- Implement handleEditTheme to load theme colors into editor
- Track editing state with editingThemeId to update existing themes
- Update save logic to reuse theme ID when editing vs creating new
- Switch to customize tab and show toast when editing begins
- Update success message to show 'updated' vs 'saved' appropriately
This commit is contained in:
spencrmartin
2026-02-17 12:10:21 -05:00
parent 13b37f16d3
commit d19e7407ac
2 changed files with 64 additions and 24 deletions
@@ -11,14 +11,15 @@ import { ThemePreset } from '../../../../../themes/presets/types';
import { getThemePresets, applyThemePreset } from '../../../../../api';
import { getActiveTheme, deleteCustomTheme } from '../../../../../api/theme-api';
import { useTheme } from '../../../../../contexts/ThemeContext';
import { Check, Download, Trash2 } from 'lucide-react';
import { Check, Download, Trash2, Sliders } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from '../../../../ui/Tooltip';
interface PresetGalleryProps {
onApply?: () => void;
onEdit?: (preset: ThemePreset) => void;
}
export function PresetGallery({ onApply }: PresetGalleryProps) {
export function PresetGallery({ onApply, onEdit }: PresetGalleryProps) {
const { resolvedTheme } = useTheme();
const [presets, setPresets] = useState<ThemePreset[]>([]);
const [loading, setLoading] = useState(true);
@@ -243,25 +244,44 @@ export function PresetGallery({ onApply }: PresetGalleryProps) {
</TooltipContent>
</Tooltip>
{/* Delete Button (only for custom themes) */}
{/* Edit and Delete Buttons (only for custom themes) */}
{isCustom && (
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={() => handleDeleteTheme(preset.id, preset.name)}
disabled={applying !== null || deleting !== null}
variant="outline"
size="sm"
shape="round"
className="text-text-danger hover:bg-background-danger"
>
<Trash2 className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
{deleting === preset.id ? 'Deleting...' : 'Delete Theme'}
</TooltipContent>
</Tooltip>
<>
{/* Edit Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={() => onEdit?.(preset)}
disabled={applying !== null || deleting !== null}
variant="outline"
size="sm"
shape="round"
>
<Sliders className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Edit Theme</TooltipContent>
</Tooltip>
{/* Delete Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={() => handleDeleteTheme(preset.id, preset.name)}
disabled={applying !== null || deleting !== null}
variant="outline"
size="sm"
shape="round"
className="text-text-danger hover:bg-background-danger"
>
<Trash2 className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
{deleting === preset.id ? 'Deleting...' : 'Delete Theme'}
</TooltipContent>
</Tooltip>
</>
)}
</div>
</div>
@@ -29,6 +29,7 @@ export function ThemeColorEditor({ onClose }: ThemeColorEditorProps) {
const [selectedVariable, setSelectedVariable] = useState<string | null>(null);
const [themeName, setThemeName] = useState('My Custom Theme');
const [themeDescription, setThemeDescription] = useState('');
const [editingThemeId, setEditingThemeId] = useState<string | null>(null);
// Use system theme instead of separate mode state
const activeMode: ColorMode = resolvedTheme;
@@ -88,8 +89,8 @@ export function ThemeColorEditor({ onClose }: ThemeColorEditorProps) {
return;
}
// Generate a unique theme ID from the name
const themeId = `custom-${themeName.toLowerCase().replace(/[^a-z0-9]+/g, '-')}-${Date.now()}`;
// Use existing ID if editing, otherwise generate new one
const themeId = editingThemeId || `custom-${themeName.toLowerCase().replace(/[^a-z0-9]+/g, '-')}-${Date.now()}`;
// Convert back to CSS format
const cssLines: string[] = [];
@@ -131,7 +132,8 @@ export function ThemeColorEditor({ onClose }: ThemeColorEditorProps) {
},
});
toast.success(`Theme "${themeName}" saved and applied successfully!`);
const action = editingThemeId ? 'updated' : 'saved';
toast.success(`Theme "${themeName}" ${action} and applied successfully!`);
// Reload the page to apply changes
window.location.reload();
@@ -161,6 +163,24 @@ export function ThemeColorEditor({ onClose }: ThemeColorEditorProps) {
}
};
const handleEditTheme = (preset: any) => {
// Load the preset's colors into the editor
setThemeColors({
light: preset.colors.light || {},
dark: preset.colors.dark || {},
});
// Set the theme metadata
setThemeName(preset.name);
setThemeDescription(preset.description || '');
setEditingThemeId(preset.id);
// Switch to customize tab
setActiveTab('customize');
toast.info(`Editing "${preset.name}"`);
};
const groupedVariables = COLOR_VARIABLES.reduce((acc, variable) => {
if (!acc[variable.category]) {
acc[variable.category] = [];
@@ -256,7 +276,7 @@ export function ThemeColorEditor({ onClose }: ThemeColorEditorProps) {
{/* Presets Tab */}
<TabsContent value="presets" className="flex-1 overflow-auto px-6 py-4">
<PresetGallery onApply={onClose} />
<PresetGallery onApply={onClose} onEdit={handleEditTheme} />
</TabsContent>
{/* Customize Tab */}