101 lines
3.7 KiB
TypeScript
101 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useFeatureFlags, useSdk, useTheme, useNotifications } from "@lynkd/messaging-inbox-sdk";
|
|
import { useUi } from "@/lib/ui-state";
|
|
import { Avatar, IconButton } from "@/components/ui/primitives";
|
|
import { Search, Sun, Moon, Bell, ChevronDown, PanelLeft } from "lucide-react";
|
|
|
|
export function Header({
|
|
title,
|
|
subtitle,
|
|
eyebrow,
|
|
children,
|
|
}: {
|
|
title: React.ReactNode;
|
|
subtitle?: React.ReactNode;
|
|
eyebrow?: string;
|
|
children?: React.ReactNode;
|
|
}) {
|
|
const flags = useFeatureFlags();
|
|
const { me } = useSdk();
|
|
const { scheme, toggleScheme } = useTheme();
|
|
const { setPaletteOpen, setNotifOpen, openProfile, setSidebarOpen, sidebarCollapsed, toggleSidebarCollapsed } = useUi();
|
|
const { unread } = useNotifications();
|
|
const [mac, setMac] = useState(true);
|
|
useEffect(() => {
|
|
setMac(/mac|iphone|ipad/i.test(navigator.platform || navigator.userAgent));
|
|
}, []);
|
|
|
|
return (
|
|
<header className="flex h-[60px] shrink-0 items-center gap-3 border-b border-border bg-surface/80 px-4 backdrop-blur">
|
|
<button
|
|
className="focus-ring grid h-9 w-9 place-items-center rounded-control text-muted hover:bg-hover md:hidden"
|
|
onClick={() => setSidebarOpen(true)}
|
|
aria-label="Open sidebar"
|
|
>
|
|
<PanelLeft size={18} />
|
|
</button>
|
|
{sidebarCollapsed && (
|
|
<button
|
|
className="focus-ring hidden h-9 w-9 place-items-center rounded-control text-muted hover:bg-hover hover:text-ink md:grid"
|
|
onClick={toggleSidebarCollapsed}
|
|
aria-label="Expand sidebar"
|
|
title="Expand sidebar"
|
|
>
|
|
<PanelLeft size={18} />
|
|
</button>
|
|
)}
|
|
|
|
<div className="min-w-0 flex-1">
|
|
{eyebrow && <div className="text-[11px] font-semibold uppercase tracking-wide text-dim">{eyebrow}</div>}
|
|
<div className="flex items-center gap-2">
|
|
<h1 className="truncate text-[17px] font-bold leading-tight">{title}</h1>
|
|
</div>
|
|
{subtitle && <p className="truncate text-[12.5px] text-muted">{subtitle}</p>}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1.5">
|
|
{children}
|
|
|
|
{flags.search && (
|
|
<button
|
|
onClick={() => setPaletteOpen(true)}
|
|
className="focus-ring hidden items-center gap-2 rounded-control border border-border bg-panel px-3 py-2 text-[13px] text-muted transition-colors hover:bg-hover hover:text-ink sm:flex"
|
|
>
|
|
<Search size={15} />
|
|
<span>Search…</span>
|
|
<kbd suppressHydrationWarning className="rounded bg-hover px-1.5 py-0.5 font-mono text-[10px] text-dim">{mac ? "⌘K" : "Ctrl K"}</kbd>
|
|
</button>
|
|
)}
|
|
|
|
{flags.themeToggle && (
|
|
<IconButton label="Toggle theme" onClick={toggleScheme}>
|
|
{scheme === "dark" ? <Sun size={18} /> : <Moon size={18} />}
|
|
</IconButton>
|
|
)}
|
|
|
|
{flags.notifications && (
|
|
<IconButton label="Notifications" onClick={() => setNotifOpen(true)} badge={unread}>
|
|
<Bell size={18} />
|
|
</IconButton>
|
|
)}
|
|
|
|
{me && (
|
|
<button
|
|
onClick={() => openProfile(me.id)}
|
|
className="focus-ring ml-1 flex items-center gap-2 rounded-control p-1 pr-2 hover:bg-hover"
|
|
>
|
|
<Avatar user={me} size={32} showPresence={false} />
|
|
<span className="hidden text-left lg:block">
|
|
<span className="block text-[13px] font-semibold leading-tight">{me.name}</span>
|
|
<span className="block text-[11px] text-muted">{me.title}</span>
|
|
</span>
|
|
<ChevronDown size={15} className="hidden text-muted lg:block" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|