veza/apps/web/src/features/studio/components/cloud-file-browser/FileGridCard.tsx

106 lines
3.3 KiB
TypeScript
Raw Normal View History

import React from 'react';
import { Card } from '@/components/ui/card';
import { CheckSquare, Square, Folder, Music, Image as ImageIcon, File } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { CloudFileNode } from './types';
export interface FileGridCardProps {
file: CloudFileNode;
selected: boolean;
onSelect: (id: string) => void;
onClick: (file: CloudFileNode) => void;
isLoading?: boolean;
className?: string;
}
function FileTypeIcon({ type, size = 'md' }: { type: CloudFileNode['type']; size?: 'md' | 'lg' }) {
const cl = size === 'lg' ? 'w-8 h-8' : 'w-5 h-5';
if (type === 'folder') return <Folder className={cn(cl, 'text-warning')} />;
if (type === 'audio') return <Music className={cn(cl, 'text-kodo-steel')} />;
if (type === 'image') return <ImageIcon className={cn(cl, 'text-primary')} />;
if (['document', 'archive', 'project'].includes(type)) {
return <File className={cn(cl, 'text-kodo-content-dim')} />;
}
return <File className={cn(cl, 'text-kodo-content-dim')} />;
}
export function FileGridCard({
file,
selected,
onSelect,
onClick,
isLoading = false,
className,
}: FileGridCardProps) {
if (isLoading) {
return (
<Card
variant="default"
className={cn(
'p-4 flex flex-col items-center text-center gap-4 animate-pulse',
className
)}
>
<div className="w-16 h-16 rounded-2xl bg-muted" />
<div className="w-full space-y-2">
<div className="h-4 w-full rounded bg-muted" />
<div className="flex justify-center gap-1">
<div className="h-3 w-12 rounded bg-muted" />
<div className="h-3 w-10 rounded bg-muted" />
</div>
</div>
</Card>
);
}
return (
<Card
variant="default"
className={cn(
'p-4 flex flex-col items-center text-center gap-4 cursor-pointer hover:border-primary/50 transition-all duration-[var(--duration-normal)] group relative',
selected && 'border-primary bg-primary/5',
className
)}
onClick={() => onClick(file)}
>
<button
type="button"
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10"
onClick={(e) => {
e.stopPropagation();
onSelect(file.id);
}}
aria-label={selected ? 'Désélectionner' : 'Sélectionner'}
>
{selected ? (
<CheckSquare className="w-4 h-4 text-primary" />
) : (
<Square className="w-4 h-4 text-muted-foreground hover:text-foreground" />
)}
</button>
<div className="w-16 h-16 rounded-2xl bg-kodo-ink flex items-center justify-center shadow-lg transition-opacity group-hover:opacity-80 duration-200">
<FileTypeIcon type={file.type} size="lg" />
</div>
<div className="w-full min-w-0">
<h4
className="font-bold text-white text-sm truncate w-full"
title={file.name}
>
{file.name}
</h4>
<div className="flex justify-center gap-1 mt-1 flex-wrap">
{file.tags?.slice(0, 2).map((t) => (
<span
key={t}
className="text-xs bg-white/10 px-1 rounded text-kodo-content-dim"
>
{t}
</span>
))}
</div>
</div>
</Card>
);
}