Files
kit-ui/components/animate/PresetLibrary.tsx
Sebastian Krüger ea464ef797 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>
2026-03-01 08:48:35 +01:00

84 lines
3.2 KiB
TypeScript

'use client';
import { useEffect, useRef, useState } from 'react';
import { cn } from '@/lib/utils/cn';
import { PRESETS, PRESET_CATEGORIES } from '@/lib/animate/presets';
import { buildKeyframesOnly } from '@/lib/animate/cssBuilder';
import type { AnimationConfig, AnimationPreset, PresetCategory } from '@/types/animate';
interface Props {
onSelect: (config: AnimationConfig) => void;
}
function PresetCard({ preset, onSelect }: { preset: AnimationPreset; onSelect: () => void }) {
const styleRef = useRef<HTMLStyleElement | null>(null);
const animName = `preview-${preset.id}`;
const thumbDuration = Math.min(preset.config.duration, 1200);
useEffect(() => {
const renamedConfig = { ...preset.config, name: animName };
if (!styleRef.current) {
styleRef.current = document.createElement('style');
document.head.appendChild(styleRef.current);
}
styleRef.current.textContent = buildKeyframesOnly(renamedConfig);
return () => { styleRef.current?.remove(); styleRef.current = null; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<button
onClick={onSelect}
className="flex flex-col items-center gap-2 p-3 rounded-xl border border-border/20 bg-primary/3 transition-all hover:border-primary/40 hover:bg-primary/8 group"
>
<div className="w-full h-12 flex items-center justify-center rounded-lg bg-white/3 overflow-hidden">
<div
className="w-7 h-7 rounded-md bg-gradient-to-br from-violet-500 to-purple-600"
style={{
animationName: animName,
animationDuration: `${thumbDuration}ms`,
animationTimingFunction: preset.config.easing,
animationIterationCount: 'infinite',
animationDirection: 'alternate',
animationFillMode: 'both',
}}
/>
</div>
<span className="text-[10px] font-mono text-center leading-tight text-foreground/60 group-hover:text-foreground/80 transition-colors">
{preset.name}
</span>
</button>
);
}
export function PresetLibrary({ onSelect }: Props) {
const [category, setCategory] = useState<PresetCategory>(PRESET_CATEGORIES[0]);
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-[10px] font-semibold text-muted-foreground uppercase tracking-widest">Presets</span>
<div className="flex glass rounded-lg p-0.5 gap-0.5">
{PRESET_CATEGORIES.map((cat) => (
<button
key={cat}
onClick={() => setCategory(cat)}
className={cn(
'px-2 py-1 rounded-md text-[10px] font-mono transition-all',
category === cat ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground'
)}
>
{cat}
</button>
))}
</div>
</div>
<div className="grid grid-cols-3 sm:grid-cols-4 gap-2">
{PRESETS.filter((p) => p.category === category).map((preset) => (
<PresetCard key={preset.id} preset={preset} onSelect={() => onSelect(preset.config)} />
))}
</div>
</div>
);
}