veza/apps/web/src/components/library/AutoMetadataDetectionModal.tsx
senke a08a0081f8 feat(ui): avatar polish, smooth accordion, modal animation consistency
Avatar improvements:
- Image loading skeleton with animate-pulse overlay
- Fade-in transition on image load (opacity 0→1, 200ms)
- Error fallback shows initials when image fails
- Click animation: active:scale-95
- Badge overlay prop: count/dot/color at top-right corner

Accordion/Collapsible smooth animation:
- Replaced max-h-[5000px] hack with CSS grid-template-rows trick
- grid-rows-[0fr] → grid-rows-[1fr] for content-aware smooth collapse
- 200ms ease-out transition on both Collapsible and AccordionContent

Modal animation consistency (5 modals migrated):
- CreatePlaylistModal → base Modal (focus trap, AnimatePresence, portal)
- AutoMetadataDetectionModal → base Modal
- ReviewProductModal → base Modal
- ChangeUsernameModal → base Modal
- TagSuggestionsModal → base Modal
- Skipped: SharePostModal (multi-view pattern, would lose layout flexibility)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:46:46 +01:00

123 lines
4.1 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Button } from '../ui/button';
import { Modal } from '@/components/ui/modal';
import { Check, Music2 } from 'lucide-react';
import { useToast } from '../../components/feedback/ToastProvider';
interface DetectedData {
bpm: number;
key: string;
genre: string;
energy: string;
}
interface AutoMetadataDetectionModalProps {
onClose: () => void;
onApply: (data: DetectedData) => void;
fileName: string;
}
export const AutoMetadataDetectionModal: React.FC<
AutoMetadataDetectionModalProps
> = ({ onClose, onApply, fileName }) => {
const { addToast: _addToast } = useToast();
const [loading, setLoading] = useState(true);
const [result, setResult] = useState<DetectedData | null>(null);
useEffect(() => {
// Simulate AI detection
const timer = setTimeout(() => {
setResult({
bpm: 128,
key: 'F# Minor',
genre: 'Synthwave',
energy: 'High',
});
setLoading(false);
}, 2500);
return () => clearTimeout(timer);
}, []);
return (
<Modal
open={true}
onClose={onClose}
title="AI Metadata Detection"
>
<div className="flex flex-col items-center text-center">
{loading ? (
<div className="space-y-6">
<div className="relative">
<div className="w-20 h-20 rounded-full border-4 border-border border-t-kodo-steel animate-spin mx-auto"></div>
<div className="absolute inset-0 flex items-center justify-center">
<Music2 className="w-8 h-8 text-muted-foreground/50" />
</div>
</div>
<div>
<h4 className="text-lg font-bold text-white animate-pulse">
Analyzing Audio...
</h4>
<p className="text-sm text-muted-foreground mt-2">
Detecting BPM, Key, and Genre for <br />
<span className="text-muted-foreground">{fileName}</span>
</p>
</div>
</div>
) : (
<div className="w-full space-y-6 animate-fadeIn">
<div className="bg-card border border-border/20 rounded-lg p-6 w-full">
<div className="grid grid-cols-2 gap-4">
<div className="text-center p-2">
<div className="text-xs text-muted-foreground uppercase font-bold mb-1">
Detected BPM
</div>
<div className="text-2xl font-bold text-white">
{result?.bpm}
</div>
</div>
<div className="text-center p-2">
<div className="text-xs text-muted-foreground uppercase font-bold mb-1">
Detected Key
</div>
<div className="text-2xl font-bold text-muted-foreground">
{result?.key}
</div>
</div>
<div className="text-center p-2">
<div className="text-xs text-muted-foreground uppercase font-bold mb-1">
Genre
</div>
<div className="text-lg font-medium text-white">
{result?.genre}
</div>
</div>
<div className="text-center p-2">
<div className="text-xs text-muted-foreground uppercase font-bold mb-1">
Energy Level
</div>
<div className="text-lg font-medium text-warning">
{result?.energy}
</div>
</div>
</div>
</div>
<div className="flex gap-4 w-full">
<Button variant="ghost" onClick={onClose} className="flex-1">
Discard
</Button>
<Button
variant="primary"
className="flex-1"
icon={<Check className="w-4 h-4" />}
onClick={() => result && onApply(result)}
>
Apply Tags
</Button>
</div>
</div>
)}
</div>
</Modal>
);
};