
Some checks failed
GBCI / build (push) Failing after 10m45s
- Introduced a new CSS theme for Orange, featuring a modern color palette with distinct foreground and background colors. - Added an XTree Gold theme that emulates the classic 1980s DOS interface, complete with authentic colors and styles for file management elements. - Both themes include variables for customization and specific styles for various UI components such as cards, popovers, and menus.
692 lines
No EOL
25 KiB
TypeScript
692 lines
No EOL
25 KiB
TypeScript
"use client";
|
||
import React, { useState, useRef, useEffect } from 'react';
|
||
import {
|
||
Search, Filter, Grid, List, Play, Pause, Volume2, VolumeX,
|
||
Eye, Bookmark, Share, Download, Clock, TrendingUp,
|
||
Globe, Calendar, Tag, ExternalLink, MoreHorizontal,
|
||
ChevronLeft, ChevronRight, Heart, MessageCircle,
|
||
Video, Image, Music, FileText, Rss, Archive,
|
||
Star, ThumbsUp, Settings, RefreshCw, Maximize,
|
||
User, MapPin, Zap, Briefcase, Gamepad2, Palette,
|
||
Users, Award, ShoppingBag, Plane, DollarSign,
|
||
Newspaper, AlertTriangle, Target, Mic, Camera
|
||
} from 'lucide-react';
|
||
import { cn } from "@/lib/utils";
|
||
|
||
// Mock news data - realistic news structure
|
||
const mockNews = [
|
||
{
|
||
id: 1,
|
||
headline: "Major Climate Summit Reaches Breakthrough Agreement on Carbon Reduction",
|
||
summary: "World leaders agree to unprecedented measures targeting 50% reduction in emissions by 2030, with binding commitments from major economies.",
|
||
content: "In a historic development at the Global Climate Summit, representatives from 195 countries have agreed to a comprehensive framework...",
|
||
author: "Sarah Chen",
|
||
publication: "Global Times",
|
||
publishedAt: "2025-01-15T14:30:00Z",
|
||
category: "Environment",
|
||
subcategory: "Climate Change",
|
||
readTime: "8 min read",
|
||
mediaType: "article",
|
||
imageUrl: "/api/placeholder/800/400",
|
||
videoUrl: null,
|
||
audioUrl: null,
|
||
tags: ["climate", "politics", "international", "environment"],
|
||
location: "Geneva, Switzerland",
|
||
breaking: true,
|
||
featured: true,
|
||
viewCount: 45670,
|
||
shareCount: 1240,
|
||
commentCount: 892,
|
||
bookmarkCount: 2130,
|
||
sentiment: "positive",
|
||
credibilityScore: 0.94
|
||
},
|
||
{
|
||
id: 2,
|
||
headline: "Tech Giant Announces Revolutionary AI Chip Architecture",
|
||
summary: "New quantum-enhanced processing unit promises 1000x performance improvement for AI workloads, reshaping the semiconductor industry.",
|
||
content: "Silicon Valley's leading tech company unveiled its latest innovation today, a groundbreaking AI chip that combines...",
|
||
author: "Marcus Rodriguez",
|
||
publication: "Tech Weekly",
|
||
publishedAt: "2025-01-15T12:45:00Z",
|
||
category: "Technology",
|
||
subcategory: "AI & Computing",
|
||
readTime: "6 min read",
|
||
mediaType: "video",
|
||
imageUrl: "/api/placeholder/800/400",
|
||
videoUrl: "/api/placeholder/video",
|
||
audioUrl: null,
|
||
videoDuration: "15:32",
|
||
tags: ["ai", "technology", "semiconductors", "innovation"],
|
||
location: "San Francisco, CA",
|
||
breaking: false,
|
||
featured: true,
|
||
viewCount: 78234,
|
||
shareCount: 2156,
|
||
commentCount: 445,
|
||
bookmarkCount: 3421,
|
||
sentiment: "positive",
|
||
credibilityScore: 0.91
|
||
},
|
||
{
|
||
id: 3,
|
||
headline: "Global Markets React to Federal Reserve Interest Rate Decision",
|
||
summary: "Stock markets surge as Fed maintains current rates, signaling confidence in economic recovery and inflation control measures.",
|
||
content: "Financial markets around the world responded positively to the Federal Reserve's decision to maintain...",
|
||
author: "Elena Vasquez",
|
||
publication: "Financial Herald",
|
||
publishedAt: "2025-01-15T11:20:00Z",
|
||
category: "Business",
|
||
subcategory: "Markets",
|
||
readTime: "5 min read",
|
||
mediaType: "article",
|
||
imageUrl: "/api/placeholder/800/400",
|
||
videoUrl: null,
|
||
audioUrl: null,
|
||
tags: ["finance", "fed", "markets", "economy"],
|
||
location: "New York, NY",
|
||
breaking: false,
|
||
featured: false,
|
||
viewCount: 34567,
|
||
shareCount: 876,
|
||
commentCount: 234,
|
||
bookmarkCount: 1456,
|
||
sentiment: "neutral",
|
||
credibilityScore: 0.96
|
||
},
|
||
{
|
||
id: 4,
|
||
headline: "Space Mission Discovers Evidence of Ancient Water on Mars",
|
||
summary: "NASA's latest rover findings reveal extensive underground water systems, raising new questions about potential for past life.",
|
||
content: "The Mars exploration mission has yielded its most significant discovery yet, with clear evidence of...",
|
||
author: "Dr. James Mitchell",
|
||
publication: "Science Today",
|
||
publishedAt: "2025-01-15T09:15:00Z",
|
||
category: "Science",
|
||
subcategory: "Space",
|
||
readTime: "10 min read",
|
||
mediaType: "multimedia",
|
||
imageUrl: "/api/placeholder/800/400",
|
||
videoUrl: "/api/placeholder/video",
|
||
audioUrl: "/api/placeholder/audio",
|
||
videoDuration: "8:45",
|
||
audioDuration: "12:30",
|
||
tags: ["space", "mars", "nasa", "discovery"],
|
||
location: "Pasadena, CA",
|
||
breaking: false,
|
||
featured: true,
|
||
viewCount: 92341,
|
||
shareCount: 4567,
|
||
commentCount: 1234,
|
||
bookmarkCount: 5678,
|
||
sentiment: "positive",
|
||
credibilityScore: 0.98
|
||
},
|
||
{
|
||
id: 5,
|
||
headline: "Championship Final Breaks Viewership Records Worldwide",
|
||
summary: "Historic match draws over 2 billion viewers globally, showcasing the growing international appeal of the sport.",
|
||
content: "Last night's championship final has set a new benchmark for global sports viewership...",
|
||
author: "Alex Thompson",
|
||
publication: "Sports Central",
|
||
publishedAt: "2025-01-15T08:30:00Z",
|
||
category: "Sports",
|
||
subcategory: "Football",
|
||
readTime: "4 min read",
|
||
mediaType: "video",
|
||
imageUrl: "/api/placeholder/800/400",
|
||
videoUrl: "/api/placeholder/video",
|
||
audioUrl: null,
|
||
videoDuration: "22:15",
|
||
tags: ["sports", "football", "championship", "records"],
|
||
location: "London, UK",
|
||
breaking: false,
|
||
featured: false,
|
||
viewCount: 156789,
|
||
shareCount: 8901,
|
||
commentCount: 3456,
|
||
bookmarkCount: 2345,
|
||
sentiment: "positive",
|
||
credibilityScore: 0.89
|
||
}
|
||
];
|
||
|
||
const categories = [
|
||
{ id: 'all', name: 'All News', icon: Newspaper, color: 'text-gray-600' },
|
||
{ id: 'breaking', name: 'Breaking', icon: AlertTriangle, color: 'text-red-600' },
|
||
{ id: 'politics', name: 'Politics', icon: Users, color: 'text-blue-600' },
|
||
{ id: 'technology', name: 'Technology', icon: Zap, color: 'text-purple-600' },
|
||
{ id: 'business', name: 'Business', icon: Briefcase, color: 'text-green-600' },
|
||
{ id: 'science', name: 'Science', icon: Target, color: 'text-indigo-600' },
|
||
{ id: 'sports', name: 'Sports', icon: Award, color: 'text-orange-600' },
|
||
{ id: 'entertainment', name: 'Entertainment', icon: Palette, color: 'text-pink-600' },
|
||
{ id: 'health', name: 'Health', icon: Heart, color: 'text-emerald-600' },
|
||
{ id: 'world', name: 'World', icon: Globe, color: 'text-cyan-600' }
|
||
];
|
||
|
||
const formatDate = (dateString) => {
|
||
const date = new Date(dateString);
|
||
const now = new Date();
|
||
const diffMs = now - date;
|
||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||
|
||
if (diffHours < 1) return 'Just now';
|
||
if (diffHours < 24) return `${diffHours}h ago`;
|
||
if (diffDays < 7) return `${diffDays}d ago`;
|
||
return date.toLocaleDateString();
|
||
};
|
||
|
||
const formatNumber = (num) => {
|
||
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
|
||
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`;
|
||
return num.toString();
|
||
};
|
||
|
||
const MediaPlayer = ({ article, onClose }) => {
|
||
const [isPlaying, setIsPlaying] = useState(false);
|
||
const [isMuted, setIsMuted] = useState(false);
|
||
const [currentTime, setCurrentTime] = useState(0);
|
||
const [duration, setDuration] = useState(0);
|
||
const videoRef = useRef(null);
|
||
const audioRef = useRef(null);
|
||
|
||
const togglePlay = () => {
|
||
if (article.mediaType === 'video' && videoRef.current) {
|
||
if (isPlaying) {
|
||
videoRef.current.pause();
|
||
} else {
|
||
videoRef.current.play();
|
||
}
|
||
setIsPlaying(!isPlaying);
|
||
}
|
||
};
|
||
|
||
const toggleMute = () => {
|
||
if (videoRef.current) {
|
||
videoRef.current.muted = !isMuted;
|
||
setIsMuted(!isMuted);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4">
|
||
<div className="bg-background rounded-xl w-full max-w-4xl max-h-[90vh] overflow-hidden">
|
||
<div className="flex items-center justify-between p-4 border-b">
|
||
<h2 className="text-lg font-semibold truncate">{article.headline}</h2>
|
||
<button
|
||
onClick={onClose}
|
||
className="p-2 hover:bg-muted rounded-lg transition-colors"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<div className="relative">
|
||
{article.mediaType === 'video' && (
|
||
<div className="relative">
|
||
<video
|
||
ref={videoRef}
|
||
className="w-full h-auto max-h-[60vh]"
|
||
poster={article.imageUrl}
|
||
onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}
|
||
onLoadedMetadata={(e) => setDuration(e.target.duration)}
|
||
>
|
||
<source src={article.videoUrl} type="video/mp4" />
|
||
</video>
|
||
|
||
<div className="absolute bottom-4 left-4 flex items-center gap-2">
|
||
<button
|
||
onClick={togglePlay}
|
||
className="p-2 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
|
||
>
|
||
{isPlaying ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
|
||
</button>
|
||
<button
|
||
onClick={toggleMute}
|
||
className="p-2 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
|
||
>
|
||
{isMuted ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />}
|
||
</button>
|
||
<span className="text-white text-sm bg-black/50 px-2 py-1 rounded">
|
||
{article.videoDuration}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{article.mediaType === 'audio' && (
|
||
<div className="p-8 text-center">
|
||
<div className="w-32 h-32 mx-auto mb-4 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center">
|
||
<Music className="w-16 h-16 text-white" />
|
||
</div>
|
||
<audio
|
||
ref={audioRef}
|
||
controls
|
||
className="w-full max-w-md mx-auto"
|
||
src={article.audioUrl}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="p-4">
|
||
<div className="text-sm text-muted-foreground mb-2">
|
||
{article.author} • {article.publication} • {formatDate(article.publishedAt)}
|
||
</div>
|
||
<p className="text-sm leading-relaxed">{article.summary}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const NewsCard = ({ article, layout = 'grid', onPlay, onBookmark, onShare }) => {
|
||
const getMediaIcon = () => {
|
||
switch (article.mediaType) {
|
||
case 'video': return <Video className="w-4 h-4" />;
|
||
case 'audio': return <Music className="w-4 h-4" />;
|
||
case 'multimedia': return <Camera className="w-4 h-4" />;
|
||
default: return <FileText className="w-4 h-4" />;
|
||
}
|
||
};
|
||
|
||
const getSentimentColor = () => {
|
||
switch (article.sentiment) {
|
||
case 'positive': return 'text-green-600';
|
||
case 'negative': return 'text-red-600';
|
||
default: return 'text-gray-600';
|
||
}
|
||
};
|
||
|
||
if (layout === 'list') {
|
||
return (
|
||
<div className="flex gap-4 p-4 border-b hover:bg-muted/50 transition-colors">
|
||
<div className="w-32 h-24 bg-muted rounded-lg overflow-hidden flex-shrink-0">
|
||
<img src={article.imageUrl} alt="" className="w-full h-full object-cover" />
|
||
{(article.mediaType === 'video' || article.mediaType === 'multimedia') && (
|
||
<div className="absolute inset-0 flex items-center justify-center">
|
||
<button
|
||
onClick={() => onPlay(article)}
|
||
className="p-2 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
|
||
>
|
||
<Play className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex items-start justify-between gap-2">
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-2 mb-1">
|
||
{article.breaking && (
|
||
<span className="bg-red-600 text-white text-xs px-2 py-1 rounded-full font-medium">
|
||
BREAKING
|
||
</span>
|
||
)}
|
||
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full">
|
||
{article.category}
|
||
</span>
|
||
<div className="flex items-center gap-1">
|
||
{getMediaIcon()}
|
||
<span className="text-xs text-muted-foreground">
|
||
{article.videoDuration || article.audioDuration || article.readTime}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 className="font-semibold text-lg mb-2 line-clamp-2 hover:text-blue-600 cursor-pointer">
|
||
{article.headline}
|
||
</h3>
|
||
|
||
<p className="text-sm text-muted-foreground mb-2 line-clamp-2">
|
||
{article.summary}
|
||
</p>
|
||
|
||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||
<span className="font-medium">{article.author}</span>
|
||
<span>•</span>
|
||
<span>{article.publication}</span>
|
||
<span>•</span>
|
||
<span>{formatDate(article.publishedAt)}</span>
|
||
<span>•</span>
|
||
<span className="flex items-center gap-1">
|
||
<Eye className="w-3 h-3" />
|
||
{formatNumber(article.viewCount)}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-1">
|
||
<button
|
||
onClick={() => onBookmark(article)}
|
||
className="p-2 hover:bg-muted rounded-lg transition-colors"
|
||
>
|
||
<Bookmark className="w-4 h-4" />
|
||
</button>
|
||
<button
|
||
onClick={() => onShare(article)}
|
||
className="p-2 hover:bg-muted rounded-lg transition-colors"
|
||
>
|
||
<Share className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="bg-card border rounded-xl overflow-hidden hover:shadow-lg transition-all duration-300 group">
|
||
<div className="relative">
|
||
<img
|
||
src={article.imageUrl}
|
||
alt={article.headline}
|
||
className="w-full h-48 object-cover"
|
||
/>
|
||
|
||
{(article.mediaType === 'video' || article.mediaType === 'multimedia') && (
|
||
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||
<button
|
||
onClick={() => onPlay(article)}
|
||
className="p-4 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
|
||
>
|
||
<Play className="w-6 h-6" />
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
<div className="absolute top-3 left-3 flex items-center gap-2">
|
||
{article.breaking && (
|
||
<span className="bg-red-600 text-white text-xs px-2 py-1 rounded-full font-medium">
|
||
BREAKING
|
||
</span>
|
||
)}
|
||
<span className="bg-black/70 text-white text-xs px-2 py-1 rounded-full">
|
||
{article.category}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="absolute top-3 right-3 flex items-center gap-1">
|
||
{getMediaIcon()}
|
||
<span className="text-white text-xs bg-black/70 px-2 py-1 rounded-full">
|
||
{article.videoDuration || article.audioDuration || article.readTime}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-4">
|
||
<h3 className="font-semibold text-lg mb-2 line-clamp-2 hover:text-blue-600 cursor-pointer">
|
||
{article.headline}
|
||
</h3>
|
||
|
||
<p className="text-sm text-muted-foreground mb-3 line-clamp-3">
|
||
{article.summary}
|
||
</p>
|
||
|
||
<div className="flex items-center justify-between text-xs text-muted-foreground mb-3">
|
||
<div className="flex items-center gap-2">
|
||
<span className="font-medium">{article.author}</span>
|
||
<span>•</span>
|
||
<span>{formatDate(article.publishedAt)}</span>
|
||
</div>
|
||
<div className="flex items-center gap-1">
|
||
<Eye className="w-3 h-3" />
|
||
{formatNumber(article.viewCount)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||
<span className="flex items-center gap-1">
|
||
<Heart className="w-3 h-3" />
|
||
{formatNumber(article.shareCount)}
|
||
</span>
|
||
<span className="flex items-center gap-1">
|
||
<MessageCircle className="w-3 h-3" />
|
||
{formatNumber(article.commentCount)}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-1">
|
||
<button
|
||
onClick={() => onBookmark(article)}
|
||
className="p-1 hover:bg-muted rounded transition-colors"
|
||
>
|
||
<Bookmark className="w-4 h-4" />
|
||
</button>
|
||
<button
|
||
onClick={() => onShare(article)}
|
||
className="p-1 hover:bg-muted rounded transition-colors"
|
||
>
|
||
<Share className="w-4 h-4" />
|
||
</button>
|
||
<button className="p-1 hover:bg-muted rounded transition-colors">
|
||
<MoreHorizontal className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default function NewsMediaBrowser() {
|
||
const [selectedCategory, setSelectedCategory] = useState('all');
|
||
const [searchTerm, setSearchTerm] = useState('');
|
||
const [layout, setLayout] = useState('grid');
|
||
const [sortBy, setSortBy] = useState('latest');
|
||
const [articles, setArticles] = useState(mockNews);
|
||
const [selectedArticle, setSelectedArticle] = useState(null);
|
||
const [showPlayer, setShowPlayer] = useState(false);
|
||
const [bookmarkedArticles, setBookmarkedArticles] = useState(new Set());
|
||
|
||
const filteredArticles = articles.filter(article => {
|
||
const matchesCategory = selectedCategory === 'all' ||
|
||
article.category.toLowerCase() === selectedCategory ||
|
||
(selectedCategory === 'breaking' && article.breaking);
|
||
|
||
const matchesSearch = searchTerm === '' ||
|
||
article.headline.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||
article.summary.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||
article.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
|
||
|
||
return matchesCategory && matchesSearch;
|
||
});
|
||
|
||
const sortedArticles = [...filteredArticles].sort((a, b) => {
|
||
switch (sortBy) {
|
||
case 'latest':
|
||
return new Date(b.publishedAt) - new Date(a.publishedAt);
|
||
case 'popular':
|
||
return b.viewCount - a.viewCount;
|
||
case 'trending':
|
||
return b.shareCount - a.shareCount;
|
||
default:
|
||
return 0;
|
||
}
|
||
});
|
||
|
||
const handlePlay = (article) => {
|
||
setSelectedArticle(article);
|
||
setShowPlayer(true);
|
||
};
|
||
|
||
const handleBookmark = (article) => {
|
||
const newBookmarked = new Set(bookmarkedArticles);
|
||
if (newBookmarked.has(article.id)) {
|
||
newBookmarked.delete(article.id);
|
||
} else {
|
||
newBookmarked.add(article.id);
|
||
}
|
||
setBookmarkedArticles(newBookmarked);
|
||
};
|
||
|
||
const handleShare = (article) => {
|
||
navigator.share?.({
|
||
title: article.headline,
|
||
text: article.summary,
|
||
url: window.location.href + '/' + article.id
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="flex flex-col h-screen bg-background">
|
||
{/* Header */}
|
||
<div className="border-b bg-background/95 backdrop-blur-sm sticky top-0 z-40">
|
||
<div className="px-6 py-4">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||
News Center
|
||
</h1>
|
||
<div className="flex items-center gap-2">
|
||
<button className="flex items-center gap-2 px-3 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
|
||
<RefreshCw className="w-4 h-4" />
|
||
Refresh
|
||
</button>
|
||
<button className="p-2 hover:bg-muted rounded-lg transition-colors">
|
||
<Settings className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Search and Filters */}
|
||
<div className="flex items-center gap-4 mb-4">
|
||
<div className="relative flex-1 max-w-md">
|
||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||
<input
|
||
type="text"
|
||
placeholder="Search news..."
|
||
value={searchTerm}
|
||
onChange={(e) => setSearchTerm(e.target.value)}
|
||
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
/>
|
||
</div>
|
||
|
||
<select
|
||
value={sortBy}
|
||
onChange={(e) => setSortBy(e.target.value)}
|
||
className="px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
>
|
||
<option value="latest">Latest</option>
|
||
<option value="popular">Most Popular</option>
|
||
<option value="trending">Trending</option>
|
||
</select>
|
||
|
||
<div className="flex items-center gap-1 bg-muted rounded-lg p-1">
|
||
<button
|
||
onClick={() => setLayout('grid')}
|
||
className={cn(
|
||
"p-2 rounded transition-colors",
|
||
layout === 'grid' ? "bg-background shadow-sm" : "hover:bg-background/50"
|
||
)}
|
||
>
|
||
<Grid className="w-4 h-4" />
|
||
</button>
|
||
<button
|
||
onClick={() => setLayout('list')}
|
||
className={cn(
|
||
"p-2 rounded transition-colors",
|
||
layout === 'list' ? "bg-background shadow-sm" : "hover:bg-background/50"
|
||
)}
|
||
>
|
||
<List className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Categories */}
|
||
<div className="flex gap-2 overflow-x-auto pb-2">
|
||
{categories.map((category) => (
|
||
<button
|
||
key={category.id}
|
||
onClick={() => setSelectedCategory(category.id)}
|
||
className={cn(
|
||
"flex items-center gap-2 px-4 py-2 rounded-full whitespace-nowrap transition-all",
|
||
selectedCategory === category.id
|
||
? "bg-blue-600 text-white"
|
||
: "bg-muted hover:bg-muted/80"
|
||
)}
|
||
>
|
||
<category.icon className={cn("w-4 h-4", category.color)} />
|
||
{category.name}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Content */}
|
||
<div className="flex-1 overflow-auto p-6">
|
||
<div className="max-w-7xl mx-auto">
|
||
{/* Featured Articles */}
|
||
{selectedCategory === 'all' && (
|
||
<div className="mb-8">
|
||
<h2 className="text-xl font-semibold mb-4">Featured Stories</h2>
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
{sortedArticles.filter(article => article.featured).slice(0, 2).map((article) => (
|
||
<NewsCard
|
||
key={article.id}
|
||
article={article}
|
||
layout="grid"
|
||
onPlay={handlePlay}
|
||
onBookmark={handleBookmark}
|
||
onShare={handleShare}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Article Grid/List */}
|
||
<div className="mb-4 flex items-center justify-between">
|
||
<h2 className="text-xl font-semibold">
|
||
{selectedCategory === 'all' ? 'All News' : categories.find(c => c.id === selectedCategory)?.name}
|
||
</h2>
|
||
<span className="text-sm text-muted-foreground">
|
||
{sortedArticles.length} articles
|
||
</span>
|
||
</div>
|
||
|
||
{layout === 'grid' ? (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
{sortedArticles.map((article) => (
|
||
<NewsCard
|
||
key={article.id}
|
||
article={article}
|
||
layout="grid"
|
||
onPlay={handlePlay}
|
||
onBookmark={handleBookmark}
|
||
onShare={handleShare}
|
||
/>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<div className="space-y-0 border rounded-xl overflow-hidden">
|
||
{sortedArticles.map((article) => (
|
||
<NewsCard
|
||
key={article.id}
|
||
article={article}
|
||
layout="list"
|
||
onPlay={handlePlay}
|
||
onBookmark={handleBookmark}
|
||
onShare={handleShare}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Media Player Modal */}
|
||
{showPlayer && selectedArticle && (
|
||
<MediaPlayer
|
||
article={selectedArticle}
|
||
onClose={() => setShowPlayer(false)}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
} |