feat: add CSS Animation Editor tool
Comprehensive visual editor for CSS @keyframe animations: - AnimationSettings: name, duration, delay, easing (incl. cubic-bezier), iteration, direction, fill-mode - KeyframeTimeline: drag-to-reposition keyframe markers, click-track to add, delete selected - KeyframeProperties: per-keyframe transform (translate/rotate/scale/skew), opacity, background-color, border-radius, blur, brightness via sliders - AnimationPreview: live preview on box/circle/text element with play/pause/restart and speed control (0.25×–2×) - PresetLibrary: 22 presets across Entrance/Exit/Attention/Special categories with animated thumbnails - ExportPanel: plain CSS and Tailwind v4 @utility formats with copy and download Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
79
components/animate/ExportPanel.tsx
Normal file
79
components/animate/ExportPanel.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Copy, Download } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { buildCSS, buildTailwindCSS } from '@/lib/animate/cssBuilder';
|
||||
import type { AnimationConfig } from '@/types/animate';
|
||||
|
||||
interface Props {
|
||||
config: AnimationConfig;
|
||||
}
|
||||
|
||||
function CodeBlock({ code, filename }: { code: string; filename: string }) {
|
||||
const copy = () => {
|
||||
navigator.clipboard.writeText(code);
|
||||
toast.success('Copied to clipboard!');
|
||||
};
|
||||
|
||||
const download = () => {
|
||||
const blob = new Blob([code], { type: 'text/css' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
toast.success(`Downloaded ${filename}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="relative">
|
||||
<pre className="p-4 rounded-xl bg-muted/30 border border-border text-xs font-mono leading-relaxed overflow-auto max-h-72 text-foreground/90 whitespace-pre">
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="outline" onClick={copy} className="flex-1">
|
||||
<Copy className="h-3.5 w-3.5 mr-1.5" />
|
||||
Copy
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={download} className="flex-1">
|
||||
<Download className="h-3.5 w-3.5 mr-1.5" />
|
||||
Download .css
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ExportPanel({ config }: Props) {
|
||||
const css = useMemo(() => buildCSS(config), [config]);
|
||||
const tailwind = useMemo(() => buildTailwindCSS(config), [config]);
|
||||
|
||||
return (
|
||||
<Card className="h-full">
|
||||
<CardHeader>
|
||||
<CardTitle>Export</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="css">
|
||||
<TabsList className="mb-4">
|
||||
<TabsTrigger value="css" className="text-xs">Plain CSS</TabsTrigger>
|
||||
<TabsTrigger value="tailwind" className="text-xs">Tailwind v4</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="css">
|
||||
<CodeBlock code={css} filename={`${config.name}.css`} />
|
||||
</TabsContent>
|
||||
<TabsContent value="tailwind">
|
||||
<CodeBlock code={tailwind} filename={`${config.name}.tailwind.css`} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user