Files
kit-ui/components/animate/AnimationEditor.tsx
Sebastian Krüger f4ee557e26 fix: preset thumbnails no longer conflict with main preview animation
- Inject only @keyframes (not .animated class rule) per preset thumbnail
  so the main preview's .animated rule cannot override them
- Drive thumbnail animation entirely via inline style properties
- Remove isActive/currentName — presets should never appear selected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 17:08:22 +01:00

105 lines
3.5 KiB
TypeScript

'use client';
import { useState, useCallback } from 'react';
import { AnimationSettings } from './AnimationSettings';
import { AnimationPreview } from './AnimationPreview';
import { KeyframeTimeline } from './KeyframeTimeline';
import { KeyframeProperties } from './KeyframeProperties';
import { PresetLibrary } from './PresetLibrary';
import { ExportPanel } from './ExportPanel';
import { DEFAULT_CONFIG, newKeyframe } from '@/lib/animate/defaults';
import type { AnimationConfig, KeyframeProperties as KFProps, PreviewElement } from '@/types/animate';
export function AnimationEditor() {
const [config, setConfig] = useState<AnimationConfig>(DEFAULT_CONFIG);
const [selectedId, setSelectedId] = useState<string | null>(
DEFAULT_CONFIG.keyframes[DEFAULT_CONFIG.keyframes.length - 1].id
);
const [previewElement, setPreviewElement] = useState<PreviewElement>('box');
const selectedKeyframe = config.keyframes.find((k) => k.id === selectedId) ?? null;
const updateKeyframeProps = useCallback((id: string, props: KFProps) => {
setConfig((c) => ({
...c,
keyframes: c.keyframes.map((k) => k.id === id ? { ...k, properties: props } : k),
}));
}, []);
const addKeyframe = useCallback((offset: number) => {
const kf = newKeyframe(offset);
setConfig((c) => ({ ...c, keyframes: [...c.keyframes, kf] }));
setSelectedId(kf.id);
}, []);
const deleteKeyframe = useCallback((id: string) => {
setConfig((c) => {
if (c.keyframes.length <= 2) return c;
const next = c.keyframes.filter((k) => k.id !== id);
return { ...c, keyframes: next };
});
setSelectedId((prev) => {
if (prev !== id) return prev;
const remaining = config.keyframes.filter((k) => k.id !== id);
return remaining[remaining.length - 1]?.id ?? null;
});
}, [config.keyframes]);
const moveKeyframe = useCallback((id: string, newOffset: number) => {
const clamped = Math.min(100, Math.max(0, Math.round(newOffset)));
setConfig((c) => ({
...c,
keyframes: c.keyframes.map((k) => k.id === id ? { ...k, offset: clamped } : k),
}));
}, []);
const loadPreset = useCallback((presetConfig: AnimationConfig) => {
setConfig(presetConfig);
setSelectedId(presetConfig.keyframes[presetConfig.keyframes.length - 1].id);
}, []);
return (
<div className="space-y-6">
{/* Row 1: Settings + Preview */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-stretch">
<div className="lg:col-span-1">
<AnimationSettings config={config} onChange={setConfig} />
</div>
<div className="lg:col-span-2">
<AnimationPreview
config={config}
element={previewElement}
onElementChange={setPreviewElement}
/>
</div>
</div>
{/* Row 2: Keyframe Timeline */}
<KeyframeTimeline
keyframes={config.keyframes}
selectedId={selectedId}
onSelect={setSelectedId}
onAdd={addKeyframe}
onDelete={deleteKeyframe}
onMove={moveKeyframe}
/>
{/* Row 3: Keyframe Properties + Export */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-stretch">
<div className="lg:col-span-1">
<KeyframeProperties
keyframe={selectedKeyframe}
onChange={updateKeyframeProps}
/>
</div>
<div className="lg:col-span-2">
<ExportPanel config={config} />
</div>
</div>
{/* Row 4: Preset Library */}
<PresetLibrary onSelect={loadPreset} />
</div>
);
}