Design: Complete redesign to compact, high-density Arch Linux package manager UI

This commit is contained in:
2026-05-21 03:07:10 +05:30
parent 245cf3fbb0
commit 971c1155be
14 changed files with 743 additions and 737 deletions
+71 -20
View File
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import api from '../api/client';
import PackageGrid from '../components/PackageGrid';
import { RefreshCw, Search } from 'lucide-react';
import { RefreshCw, Search, LayoutGrid, List } from 'lucide-react';
export default function Installed() {
const [packages, setPackages] = useState([]);
@@ -9,6 +9,7 @@ export default function Installed() {
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState('');
const [error, setError] = useState(null);
const [viewMode, setViewMode] = useState('grid');
useEffect(() => { load(); }, []);
@@ -22,50 +23,100 @@ export default function Installed() {
async function load() {
setLoading(true); setError(null);
try {
const data = await api.listInstalled();
setPackages(data.results || []);
} catch (err) { setError(err.message); }
try { const data = await api.listInstalled(); setPackages(data.results || []); }
catch (err) { setError(err.message); }
finally { setLoading(false); }
}
return (
<div className="animate-slide-up">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8">
<div className="animate-slide-up flex flex-col gap-4">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h1 className="page-title">Installed Packages</h1>
<p className="page-subtitle">
{packages.length > 0 ? `${packages.length} packages installed on your system` : 'Loading installed packages...'}
{packages.length > 0 ? `${packages.length} packages on your system` : 'Loading...'}
</p>
</div>
<button onClick={load} disabled={loading} className="btn btn-secondary">
<RefreshCw size={15} className={loading ? 'animate-spin' : ''} />
Refresh
</button>
<div className="flex items-center gap-2">
{/* View toggle */}
<div className="flex p-0.5 rounded-md" style={{ background: 'var(--bg-secondary)', border: '1px solid var(--border-primary)' }}>
<button onClick={() => setViewMode('grid')}
className="p-1 rounded transition-all"
style={{ background: viewMode === 'grid' ? 'var(--accent-muted)' : 'transparent', color: viewMode === 'grid' ? 'var(--accent)' : 'var(--text-tertiary)' }}>
<LayoutGrid size={13} />
</button>
<button onClick={() => setViewMode('list')}
className="p-1 rounded transition-all"
style={{ background: viewMode === 'list' ? 'var(--accent-muted)' : 'transparent', color: viewMode === 'list' ? 'var(--accent)' : 'var(--text-tertiary)' }}>
<List size={13} />
</button>
</div>
<button onClick={load} disabled={loading} className="btn btn-secondary text-xs">
<RefreshCw size={12} className={loading ? 'animate-spin' : ''} /> Refresh
</button>
</div>
</div>
{error && (
<div className="rounded-xl p-4 mb-6 text-sm font-medium"
style={{ background: 'var(--red-muted)', color: 'var(--red)', border: '1px solid rgba(239,68,68,0.2)' }}>
<div className="rounded-lg p-3 text-xs font-medium"
style={{ background: 'var(--red-muted)', color: 'var(--red)', border: '1px solid rgba(248,113,113,0.15)' }}>
{error}
</div>
)}
{/* Filter */}
{!loading && packages.length > 0 && (
<div className="relative max-w-sm mb-8">
<Search size={15} className="absolute left-3.5 top-1/2 -translate-y-1/2" style={{ color: 'var(--text-tertiary)' }} />
<div className="relative max-w-xs">
<Search size={13} className="absolute left-3 top-1/2 -translate-y-1/2" style={{ color: 'var(--text-tertiary)' }} />
<input
id="installed-filter"
type="text"
className="input pl-10 py-2.5 text-sm rounded-xl"
placeholder="Filter installed packages..."
className="input pl-8 py-1.5 text-xs rounded-lg"
placeholder="Filter packages..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
</div>
)}
<PackageGrid packages={filtered} loading={loading} />
{/* Package List */}
{viewMode === 'grid' ? (
<PackageGrid packages={filtered} loading={loading} />
) : (
/* List view */
loading ? (
<div className="flex flex-col gap-1">
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="card p-2.5 flex items-center gap-3">
<div className="shimmer h-4 w-32"></div>
<div className="shimmer h-3 w-16"></div>
<div className="flex-1"></div>
<div className="shimmer h-3 w-48"></div>
</div>
))}
</div>
) : filtered.length === 0 ? (
<p className="text-xs text-center py-8" style={{ color: 'var(--text-tertiary)' }}>No packages found</p>
) : (
<div className="card overflow-hidden">
{filtered.map((pkg, i) => (
<div key={`${pkg.source}-${pkg.name}`}
className="flex items-center gap-3 px-3 py-2 text-xs cursor-pointer hover:bg-[var(--bg-card-hover)] transition-colors"
style={{ borderBottom: i < filtered.length - 1 ? '1px solid var(--border-primary)' : 'none' }}
onClick={() => window.location.href = `/package/${pkg.name}`}>
<span className="font-semibold w-48 truncate" style={{ color: 'var(--text-primary)' }}>{pkg.name}</span>
<span className="font-mono text-[10px] w-32 truncate" style={{ color: 'var(--text-tertiary)' }}>{pkg.version}</span>
<span className="flex-1 truncate text-[11px]" style={{ color: 'var(--text-secondary)' }}>
{pkg.description || '—'}
</span>
<span className={`badge ${pkg.source === 'aur' ? 'badge-aur' : 'badge-pacman'}`}>
{pkg.source === 'aur' ? 'AUR' : 'pacman'}
</span>
</div>
))}
</div>
)
)}
</div>
);
}