refactor: align animate tool with Calculate/Media blueprint

Layout:
- AnimationEditor: lg:grid-cols-5 (2/5 edit, 3/5 visual); full viewport
  height; mobile Edit|Preview glass pill tabs; timeline embedded in edit
  panel on mobile, standalone on desktop; Export|Presets custom tab
  panel at the bottom of the right column

Components (all shadcn removed):
- AnimationSettings: Card/Label/Input/Select/Button → native inputs;
  direction & fill mode as 4-pill selectors; easing as native <select>;
  ∞ iterations as icon pill toggle
- AnimationPreview: Card/ToggleGroup/Button → glass card; speed pills
  as inline glass pill group; element picker as compact icon pills;
  playback controls as glass icon buttons; subtle grid bg on canvas
- KeyframeTimeline: Card/Button → glass card; embedded prop for
  rendering inside another card on mobile without double glass
- KeyframeProperties: Card/Label/Input/Button → bare content section;
  SliderRow uses native number input; bg color toggle as pill button
- ExportPanel: Card/Tabs/Button → bare section; CSS|Tailwind custom
  tab switcher; dark terminal (#06060e) code blocks
- PresetLibrary: Card/Tabs → bare section; category pills replace Tabs;
  preset cards use glass border-border/20 bg-primary/3 styling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 08:48:35 +01:00
parent 50cf5823f9
commit ea464ef797
7 changed files with 598 additions and 587 deletions

View File

@@ -1,11 +1,9 @@
'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 { useMemo, useState } from 'react';
import { Copy, Download } from 'lucide-react';
import { toast } from 'sonner';
import { cn } from '@/lib/utils/cn';
import { buildCSS, buildTailwindCSS } from '@/lib/animate/cssBuilder';
import type { AnimationConfig } from '@/types/animate';
@@ -13,6 +11,11 @@ interface Props {
config: AnimationConfig;
}
type ExportTab = 'css' | 'tailwind';
const actionBtn =
'flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs glass rounded-md border border-border/30 text-muted-foreground hover:text-primary hover:border-primary/30 hover:bg-primary/10 transition-all';
function CodeBlock({ code, filename }: { code: string; filename: string }) {
const copy = () => {
navigator.clipboard.writeText(code);
@@ -31,49 +34,50 @@ function CodeBlock({ code, filename }: { code: string; filename: string }) {
};
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 scrollbar">
<div className="space-y-2">
<div className="relative group rounded-xl overflow-hidden border border-white/5" style={{ background: '#06060e' }}>
<pre className="p-4 overflow-x-auto font-mono text-[11px] text-white/55 leading-relaxed max-h-64">
<code>{code}</code>
</pre>
</div>
<div className="flex flex-col md:flex-row gap-3">
<Button variant="outline" onClick={copy} className="w-full md:flex-1">
<Copy className="h-3.5 w-3.5 mr-1.5" />
Copy
</Button>
<Button onClick={download} className="w-full md:flex-1">
<Download className="h-3.5 w-3.5 mr-1.5" />
Download .css
</Button>
<div className="flex gap-2">
<button onClick={copy} className={cn(actionBtn, 'flex-1')}>
<Copy className="w-3 h-3" />Copy
</button>
<button onClick={download} className={cn(actionBtn, 'flex-1')}>
<Download className="w-3 h-3" />Download .css
</button>
</div>
</div>
);
}
export function ExportPanel({ config }: Props) {
const [tab, setTab] = useState<ExportTab>('css');
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>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-[10px] font-semibold text-muted-foreground uppercase tracking-widest">Export</span>
<div className="flex glass rounded-lg p-0.5 gap-0.5">
{(['css', 'tailwind'] as ExportTab[]).map((t) => (
<button
key={t}
onClick={() => setTab(t)}
className={cn(
'px-2.5 py-1 rounded-md text-[10px] font-mono transition-all',
tab === t ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground'
)}
>
{t === 'css' ? 'Plain CSS' : 'Tailwind v4'}
</button>
))}
</div>
</div>
{tab === 'css' && <CodeBlock code={css} filename={`${config.name}.css`} />}
{tab === 'tailwind' && <CodeBlock code={tailwind} filename={`${config.name}.tailwind.css`} />}
</div>
);
}