mirror of
https://github.com/R0m1k3/FlowReader.git
synced 2026-10-20 21:58:11 +02:00
feat(web): overhaul frontend Paper Dark, migration Tailwind v4 et composants magazine
This commit is contained in:
1 parent
8777d3b970
commit
8ed1782de6
16 files changed
+1998
-238
No files matched your search
@@ -3,54 +3,70 @@ import type { Article } from '../api/articles';
|
||||
interface ArticleCardProps {
|
||||
article: Article;
|
||||
onClick: (article: Article) => void;
|
||||
onToggleRead: (id: string, currentStatus: boolean) => void;
|
||||
onToggleFavorite: (id: string, currentStatus: boolean) => void;
|
||||
onToggleRead: (id: string, is_read: boolean) => void;
|
||||
onToggleFavorite: (id: string) => void;
|
||||
}
|
||||
|
||||
export function ArticleCard({ article, onClick, onToggleRead, onToggleFavorite }: ArticleCardProps) {
|
||||
const publishedDate = article.published_at
|
||||
? new Date(article.published_at).toLocaleDateString(undefined, {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
: '';
|
||||
// Simulate a "reading time" based on typical RSS snippet length
|
||||
const readTime = Math.max(1, Math.ceil((article.content?.length || 500) / 1000));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`article-card ${article.is_read ? 'read' : 'unread'}`}
|
||||
<article
|
||||
className={`magazine-card group cursor-pointer flex flex-col h-full bg-carbon-light transition-all duration-500 overflow-hidden relative shadow-lg hover:gold-glow ${!article.is_read ? 'border-gold/30' : 'border-transparent opacity-90'
|
||||
}`}
|
||||
onClick={() => onClick(article)}
|
||||
>
|
||||
<div className="article-header">
|
||||
<span className="feed-title">{article.feed_title}</span>
|
||||
<span className="published-date">{publishedDate}</span>
|
||||
<div className="aspect-[16/10] overflow-hidden bg-carbon-dark relative">
|
||||
{/* Fallback pattern for articles without images */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-carbon-light to-carbon-dark opacity-40 group-hover:opacity-20 transition-opacity" />
|
||||
<div className="absolute inset-0 flex items-center justify-center opacity-10 group-hover:scale-110 transition-transform duration-700">
|
||||
<span className="text-4xl font-serif text-gold underline italic">F</span>
|
||||
</div>
|
||||
|
||||
{!article.is_read && (
|
||||
<div className="absolute top-4 right-4 w-2 h-2 bg-gold rounded-full gold-glow animate-pulse z-10" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 className="article-title">{article.title}</h3>
|
||||
<div className="p-6 flex flex-col flex-1">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-[10px] uppercase tracking-[0.2em] text-gold font-bold truncate max-w-[70%]">
|
||||
{article.feed_title || 'Source'}
|
||||
</span>
|
||||
<span className="text-[10px] text-paper-muted uppercase tracking-wider font-sans">
|
||||
{readTime} MIN READ
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{article.summary && (
|
||||
<p className="article-excerpt">
|
||||
{article.summary.replace(/<[^>]*>/g, '').substring(0, 150)}...
|
||||
</p>
|
||||
)}
|
||||
<h2 className="text-xl font-serif text-paper-white group-hover:text-gold transition-colors duration-300 leading-tight mb-4 flex-1">
|
||||
{article.title}
|
||||
</h2>
|
||||
|
||||
<div className="article-footer" onClick={(e) => e.stopPropagation()}>
|
||||
<button
|
||||
className={`action-btn ${article.is_favorite ? 'active' : ''}`}
|
||||
onClick={() => onToggleFavorite(article.id, article.is_favorite)}
|
||||
title={article.is_favorite ? 'Retirer des favoris' : 'Ajouter aux favoris'}
|
||||
>
|
||||
{article.is_favorite ? '★' : '☆'}
|
||||
</button>
|
||||
<button
|
||||
className="action-btn"
|
||||
onClick={() => onToggleRead(article.id, article.is_read)}
|
||||
title={article.is_read ? 'Marquer comme non lu' : 'Marquer comme lu'}
|
||||
>
|
||||
{article.is_read ? '○' : '●'}
|
||||
</button>
|
||||
<div className="flex items-center justify-between pt-4 border-t border-carbon-dark/50">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleFavorite(article.id);
|
||||
}}
|
||||
className={`text-xl transition-all duration-300 transform hover:scale-125 ${article.is_favorite ? 'text-gold' : 'text-paper-muted hover:text-gold opacity-30 hover:opacity-100'
|
||||
}`}
|
||||
>
|
||||
{article.is_favorite ? '★' : '☆'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleRead(article.id, article.is_read);
|
||||
}}
|
||||
className={`text-[10px] uppercase tracking-widest font-bold transition-all duration-300 ${article.is_read ? 'text-paper-muted hover:text-paper-white' : 'text-gold hover:brightness-125'
|
||||
}`}
|
||||
>
|
||||
{article.is_read ? 'Lu' : 'Marquer lu'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user