// global-articles.jsx — Article detail + article list for Teknav Global (English, LTR)
import { useState, useEffect, useRef, useMemo } from 'react';
import { articles as allArticles, authors, categories } from '../global-data.js';
import {
useNav, useToast, CategoryBadge, TypeBadge, AuthorAvatar, EmptyState,
ReadingProgress, SkeletonLoader, NewsletterForm,
inputStyle, pillBtn, iconBtnStyle,
} from './global-ui.jsx';
import { ArticleCard, sectionTitle, sectionSubtitle } from './global-home.jsx';
// ── Article List Row ─────────────────────────────────────────────────────────
function ArticleRow({ article, idx }) {
const { navigate } = useNav();
const [vis, setVis] = useState(false);
const ref = useRef();
const cat = categories.find(c => c.id === article.category);
const author = authors.find(a => a.id === article.author);
useEffect(() => {
const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) setVis(true); }, { threshold: 0.05 });
if (ref.current) obs.observe(ref.current);
return () => obs.disconnect();
}, []);
return (
navigate('/article/' + article.slug)}
onMouseEnter={e => e.currentTarget.style.opacity = '0.85'}
onMouseLeave={e => e.currentTarget.style.opacity = '1'}>
{article.date}
·
{article.type && }
{article.title}
{article.subtitle &&
{article.subtitle}
}
{author?.name}
{author?.specialty && · }
{author?.specialty && {author.specialty}}
{article.type}
·
{article.readTime} min read
{article.tags?.slice(0, 2).map(t => (
{t}
))}
{article.photo && (

{ e.target.style.display = 'none'; }} />
)}
);
}
// ── Filter Bar ───────────────────────────────────────────────────────────────
function FilterBar({ filters, onChange }) {
const cats = [{ id: '', name: 'All' }, ...categories];
const sorts = [{ v: 'latest', l: 'Latest' }, { v: 'popular', l: 'Popular' }, { v: 'readtime', l: 'Read time' }];
const chip = (active) => ({
border: active ? '1.5px solid #0F6B73' : '1px solid #E4DDD2',
background: active ? '#0F6B7312' : '#fff',
color: active ? '#0F6B73' : '#5F6B6D',
padding: '5px 12px', borderRadius: 20, fontSize: 12, cursor: 'pointer',
fontFamily: 'Inter,sans-serif', fontWeight: active ? 700 : 400,
transition: 'all 0.15s', whiteSpace: 'nowrap',
});
return (
Topic:
{cats.map(c => (
))}
Sort:
{sorts.map(s => (
))}
{filters.q && (
Search: "{filters.q}"
)}
);
}
// ── Article List Page ────────────────────────────────────────────────────────
export function ArticlesListPage({ initQ = '' }) {
const [filters, setFilters] = useState({ cat: '', sort: 'latest', q: initQ });
const [searchInput, setSearchInput] = useState(initQ);
const [page, setPage] = useState(1);
const PER = 8;
const filtered = useMemo(() => {
let list = allArticles;
if (filters.q) {
const q = filters.q.toLowerCase();
list = list.filter(a =>
a.title.toLowerCase().includes(q) ||
(a.subtitle || '').toLowerCase().includes(q) ||
authors.find(au => au.id === a.author)?.name.toLowerCase().includes(q) ||
categories.find(c => c.id === a.category)?.name.toLowerCase().includes(q)
);
}
if (filters.cat) list = list.filter(a => a.category === filters.cat);
if (filters.sort === 'popular') list = [...list].sort((a, b) => (b.views || 0) - (a.views || 0));
else if (filters.sort === 'readtime') list = [...list].sort((a, b) => b.readTime - a.readTime);
else list = [...list].sort((a, b) => (b.dateEn || b.date || '').localeCompare(a.dateEn || a.date || ''));
return list;
}, [filters]);
const visible = filtered.slice(0, page * PER);
const hasMore = visible.length < filtered.length;
const doSearch = e => { e.preventDefault(); setFilters(f => ({ ...f, q: searchInput })); setPage(1); };
return (
All Articles
{filtered.length} articles found
{ setFilters(f); setPage(1); }} />
{visible.length === 0 ? (
) : (
{visible.map((a, i) =>
)}
)}
{hasMore && (
)}
);
}
// ── Article Detail Page ──────────────────────────────────────────────────────
export default function ArticlePage({ slug }) {
const { navigate } = useNav();
const article = allArticles.find(a => a.slug === slug);
if (!article) {
return (
404
Article not found
This article may be available in the Persian edition.
);
}
const author = authors.find(a => a.id === article.author);
const cat = categories.find(c => c.id === article.category);
const related = allArticles.filter(a => a.id !== article.id && (a.category === article.category || (a.tags || []).some(t => (article.tags || []).includes(t)))).slice(0, 3);
return (
{/* Breadcrumb */}
›
›
{article.title.slice(0, 50)}{article.title.length > 50 ? '…' : ''}
{/* Layout */}
{/* Main content */}
{/* Header */}
{article.type &&
}
{article.persianUrl && (
فارسی ↗
)}
{article.title}
{article.subtitle &&
{article.subtitle}
}
{/* Byline */}
{author &&
}
{author?.name}
{author?.role}
{article.date}
·
{article.readTime} min read
{article.tags?.slice(0, 2).map(t => (
{t}
))}
{/* Hero image */}
{article.photo && (

{ e.target.parentElement.style.display = 'none'; }} />
)}
{/* Article body */}
{/* Author box */}
{author && (
{author.name}
{author.role}
{author.bio}
)}
{/* Related articles */}
{related.length > 0 && (
)}
{/* Newsletter */}
Subscribe to Teknav
Deep tech analysis in your inbox, every week
{/* Sidebar */}
);
}