Add tab navigation (Services, Bookmarks, Settings)
- Services tab: shows all internal services with health status - Bookmarks tab: shows all external bookmarks by category - Settings tab: dark mode toggle and portal info - Running services counter in header - Consistent tab styling with underline indicator Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,56 +3,89 @@
|
||||
import { usePortal } from '@/lib/PortalContext';
|
||||
import { Icon } from './Icons';
|
||||
|
||||
export function Header() {
|
||||
interface Tab {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
interface HeaderProps {
|
||||
activeTab: string;
|
||||
onTabChange: (tab: string) => void;
|
||||
tabs: Tab[];
|
||||
}
|
||||
|
||||
export function Header({ activeTab, onTabChange, tabs }: HeaderProps) {
|
||||
const { darkMode, setDarkMode, refreshHealth, isRefreshing } = usePortal();
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 bg-white/80 dark:bg-stone-950/80 backdrop-blur-sm border-b border-slate-200 dark:border-stone-800">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-4 flex items-center justify-between">
|
||||
{/* Logo / Title */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-9 h-9 flex items-center justify-center rounded-lg bg-gradient-to-br from-slate-700 to-slate-900 dark:from-stone-600 dark:to-stone-800">
|
||||
<Icon name="server" size={18} className="text-white" />
|
||||
<header className="sticky top-0 z-50 bg-white dark:bg-stone-950 border-b border-slate-200 dark:border-stone-800">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
{/* Top bar */}
|
||||
<div className="py-4 flex items-center justify-between">
|
||||
{/* Logo / Title */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-9 h-9 flex items-center justify-center rounded-lg bg-gradient-to-br from-slate-700 to-slate-900 dark:from-stone-600 dark:to-stone-800">
|
||||
<Icon name="server" size={18} className="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="font-bold text-lg text-slate-900 dark:text-stone-100">
|
||||
NUC Portal
|
||||
</h1>
|
||||
<p className="text-xs text-slate-500 dark:text-stone-500">
|
||||
192.168.1.3
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="font-bold text-lg text-slate-900 dark:text-stone-100">
|
||||
NUC Portal
|
||||
</h1>
|
||||
<p className="text-xs text-slate-500 dark:text-stone-500">
|
||||
192.168.1.3
|
||||
</p>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Refresh button */}
|
||||
<button
|
||||
onClick={refreshHealth}
|
||||
disabled={isRefreshing}
|
||||
className="p-2 rounded-lg bg-slate-100 dark:bg-stone-900 border border-slate-200 dark:border-stone-800 hover:bg-slate-200 dark:hover:bg-stone-800 disabled:opacity-50 transition-colors"
|
||||
title="Refresh health status"
|
||||
>
|
||||
<Icon
|
||||
name="refresh-cw"
|
||||
size={18}
|
||||
className={`text-slate-600 dark:text-stone-400 ${isRefreshing ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* Dark mode toggle */}
|
||||
<button
|
||||
onClick={() => setDarkMode(!darkMode)}
|
||||
className="p-2 rounded-lg bg-slate-100 dark:bg-stone-900 border border-slate-200 dark:border-stone-800 hover:bg-slate-200 dark:hover:bg-stone-800 transition-colors"
|
||||
title={darkMode ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||
>
|
||||
<Icon
|
||||
name={darkMode ? 'sun' : 'moon'}
|
||||
size={18}
|
||||
className="text-slate-600 dark:text-stone-400"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Refresh button */}
|
||||
<button
|
||||
onClick={refreshHealth}
|
||||
disabled={isRefreshing}
|
||||
className="p-2 rounded-lg bg-slate-100 dark:bg-stone-900 border border-slate-200 dark:border-stone-800 hover:bg-slate-200 dark:hover:bg-stone-800 disabled:opacity-50 transition-colors"
|
||||
title="Refresh health status"
|
||||
>
|
||||
<Icon
|
||||
name="refresh-cw"
|
||||
size={18}
|
||||
className={`text-slate-600 dark:text-stone-400 ${isRefreshing ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* Dark mode toggle */}
|
||||
<button
|
||||
onClick={() => setDarkMode(!darkMode)}
|
||||
className="p-2 rounded-lg bg-slate-100 dark:bg-stone-900 border border-slate-200 dark:border-stone-800 hover:bg-slate-200 dark:hover:bg-stone-800 transition-colors"
|
||||
title={darkMode ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||
>
|
||||
<Icon
|
||||
name={darkMode ? 'sun' : 'moon'}
|
||||
size={18}
|
||||
className="text-slate-600 dark:text-stone-400"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{/* Tabs */}
|
||||
<nav className="flex gap-1 -mb-px">
|
||||
{tabs.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => onTabChange(tab.id)}
|
||||
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'border-slate-900 dark:border-stone-100 text-slate-900 dark:text-stone-100'
|
||||
: 'border-transparent text-slate-500 dark:text-stone-500 hover:text-slate-700 dark:hover:text-stone-300 hover:border-slate-300 dark:hover:border-stone-700'
|
||||
}`}
|
||||
>
|
||||
<Icon name={tab.icon} size={16} />
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user