veza/apps/web/src/features/playlists/components/PlaylistHeader.tsx
senke c65da4fea6 refactor(ui): Design tokens - gradients, duration, textarea
- Replace cyan/magenta/purple gradients with primary/secondary
- duration-200/300 → duration-[var(--duration-normal)]
- Textarea: min-h-[100px] → min-h-24
- SearchPageHeader, DashboardPage, PlaylistHeader
- UserProfilePageHeader/Hero, PlaylistDetailPageHero
- SocialViewFeedItem, WishlistView, PostCard, ProductCard, CourseCard
- SearchPageResults, MarketplaceHome

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 22:56:30 +01:00

129 lines
4.6 KiB
TypeScript

/**
* Composant PlaylistHeader
* T0460: Create Playlist Detail Page
*/
import { Card, CardContent } from '@/components/ui/card';
import { Music, Lock, Users, Calendar, User } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { Playlist } from '../types';
import { PlaylistFollowButton } from './PlaylistFollowButton';
interface PlaylistHeaderProps {
playlist: Playlist;
className?: string;
}
const formatDate = (dateString: string): string => {
const date = new Date(dateString);
return date.toLocaleDateString('fr-FR', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
};
export function PlaylistHeader({ playlist, className }: PlaylistHeaderProps) {
return (
<Card
className={cn('mb-6', className)}
role="region"
aria-label={`Détails de la playlist ${playlist.title}`}
>
<CardContent className="p-4 sm:p-6">
<div className="flex flex-col md:flex-row gap-4 sm:gap-6">
{/* Cover Image - Mobile optimized */}
<div className="relative w-full md:w-64 h-48 sm:h-64 flex-shrink-0 rounded-lg overflow-hidden bg-gradient-to-br from-primary/40 to-secondary/40">
{playlist.cover_url ? (
<img
src={playlist.cover_url}
alt={`Couverture de la playlist ${playlist.title}`}
className="w-full h-full object-cover"
/>
) : (
<div
className="w-full h-full flex items-center justify-center"
role="img"
aria-label={`Pas de couverture pour la playlist ${playlist.title}`}
>
<Music className="w-24 h-24 text-white/50" aria-hidden="true" />
</div>
)}
{/* Visibility Badge */}
<div className="absolute top-3 right-3">
{playlist.is_public ? (
<div
className="bg-kodo-lime/100/90 text-white px-4 py-1.5 rounded-full text-sm flex items-center gap-2"
aria-label="Playlist publique"
>
<Users className="w-4 h-4" aria-hidden="true" />
Public
</div>
) : (
<div
className="bg-muted/90 text-white px-4 py-1.5 rounded-full text-sm flex items-center gap-2"
aria-label="Playlist privée"
>
<Lock className="w-4 h-4" aria-hidden="true" />
Privé
</div>
)}
</div>
</div>
{/* Playlist Info - Mobile optimized */}
<div className="flex-1 space-y-4 sm:space-y-4">
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<h1
className="text-2xl sm:text-3xl font-bold mb-2"
id="playlist-main-title"
>
{playlist.title}
</h1>
{playlist.description && (
<p
className="text-muted-foreground text-base sm:text-lg"
id="playlist-main-description"
>
{playlist.description}
</p>
)}
</div>
<PlaylistFollowButton
playlistId={playlist.id}
initialFollowerCount={(playlist as any).follower_count}
initialFollowing={(playlist as any).is_following}
/>
</div>
<div
className="flex flex-wrap gap-4 sm:gap-4 text-xs sm:text-sm text-muted-foreground"
aria-labelledby="playlist-main-title"
>
<div className="flex items-center gap-2">
<Music className="w-4 h-4" aria-hidden="true" />
<span>
{playlist.track_count} track
{playlist.track_count !== 1 ? 's' : ''}
</span>
</div>
{playlist.user && (
<div className="flex items-center gap-2">
<User className="w-4 h-4" aria-hidden="true" />
<span aria-label={`Créée par ${playlist.user.username}`}>
par {playlist.user.username}
</span>
</div>
)}
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4" aria-hidden="true" />
<span>Créée le {formatDate(playlist.created_at)}</span>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
}