Files
kit-ui/components/animate/AnimationPreview.tsx

150 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useEffect, useRef, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { Play, Pause, RotateCcw, Square, Circle, Type } from 'lucide-react';
import { buildCSS } from '@/lib/animate/cssBuilder';
import type { AnimationConfig, PreviewElement } from '@/types/animate';
interface Props {
config: AnimationConfig;
element: PreviewElement;
onElementChange: (e: PreviewElement) => void;
}
const SPEEDS: { label: string; value: string }[] = [
{ label: '0.25×', value: '0.25' },
{ label: '0.5×', value: '0.5' },
{ label: '1×', value: '1' },
{ label: '2×', value: '2' },
];
export function AnimationPreview({ config, element, onElementChange }: Props) {
const styleRef = useRef<HTMLStyleElement | null>(null);
const elementRef = useRef<HTMLDivElement>(null);
const [restartKey, setRestartKey] = useState(0);
const [paused, setPaused] = useState(false);
const [speed, setSpeed] = useState('1');
// Inject @keyframes CSS into document head
useEffect(() => {
if (!styleRef.current) {
styleRef.current = document.createElement('style');
styleRef.current.id = 'kit-animate-preview';
document.head.appendChild(styleRef.current);
}
styleRef.current.textContent = buildCSS(config);
}, [config]);
// Cleanup on unmount
useEffect(() => {
return () => {
styleRef.current?.remove();
};
}, []);
const restart = () => {
setPaused(false);
setRestartKey((k) => k + 1);
};
const scaledDuration = Math.round(config.duration / Number(speed));
return (
<Card className="h-full flex flex-col">
<CardHeader className="flex flex-row items-center justify-between space-y-0">
<CardTitle>Preview</CardTitle>
<ToggleGroup type="single" value={speed} onValueChange={(v) => v && setSpeed(v)} variant="outline" size="sm">
{SPEEDS.map((s) => (
<ToggleGroupItem key={s.value} value={s.value} className="h-6 px-1.5 min-w-0 text-[10px]">
{s.label}
</ToggleGroupItem>
))}
</ToggleGroup>
</CardHeader>
<CardContent className="flex-1 flex flex-col gap-4">
{/* Preview canvas */}
<div className="flex-1 min-h-52 flex items-center justify-center rounded-xl bg-gradient-to-br from-muted/20 to-muted/5 border border-border relative overflow-hidden">
{/* Grid overlay */}
<div
className="absolute inset-0 opacity-5 pointer-events-none"
style={{
backgroundImage: 'linear-gradient(var(--border) 1px, transparent 1px), linear-gradient(90deg, var(--border) 1px, transparent 1px)',
backgroundSize: '32px 32px',
}}
/>
{/* Animated element */}
<div
key={restartKey}
ref={elementRef}
className="animated relative z-10"
style={{
animationDuration: `${scaledDuration}ms`,
animationPlayState: paused ? 'paused' : 'running',
}}
>
{element === 'box' && (
<div className="w-20 h-20 rounded-xl bg-gradient-to-br from-violet-500 to-purple-600 shadow-lg shadow-purple-500/30" />
)}
{element === 'circle' && (
<div className="w-20 h-20 rounded-full bg-gradient-to-br from-cyan-400 to-violet-500 shadow-lg shadow-cyan-500/30" />
)}
{element === 'text' && (
<span className="text-4xl font-bold bg-gradient-to-r from-violet-400 via-pink-400 to-cyan-400 bg-clip-text text-transparent select-none">
Hello
</span>
)}
</div>
</div>
{/* Controls */}
<div className="flex items-center justify-between gap-3">
<ToggleGroup type="single" value={element} onValueChange={(v) => v && onElementChange(v as PreviewElement)} variant="outline" size="sm">
<ToggleGroupItem value="box" className="h-6 px-1.5 min-w-0" title="Box">
<Square className="h-3 w-3" />
</ToggleGroupItem>
<ToggleGroupItem value="circle" className="h-6 px-1.5 min-w-0" title="Circle">
<Circle className="h-3 w-3" />
</ToggleGroupItem>
<ToggleGroupItem value="text" className="h-6 px-1.5 min-w-0" title="Text">
<Type className="h-3 w-3" />
</ToggleGroupItem>
</ToggleGroup>
<div className="flex items-center gap-1.5">
<Button
size="icon-xs"
variant="outline"
onClick={() => { setPaused(false); }}
disabled={!paused}
title="Play"
>
<Play className="h-3 w-3" />
</Button>
<Button
size="icon-xs"
variant="outline"
onClick={() => setPaused(true)}
disabled={paused}
title="Pause"
>
<Pause className="h-3 w-3" />
</Button>
<Button
size="icon-xs"
variant="outline"
onClick={restart}
title="Restart"
>
<RotateCcw className="h-3 w-3" />
</Button>
</div>
</div>
</CardContent>
</Card>
);
}