wacrm/src/components/layout/header.tsx
Arnas Donauskas 73ae399a4d
feat(mobile): app shell drawer + responsive header
First of a series to make the app mobile-friendly. The sidebar used
to be a fixed 240px column always visible, eating most of a 375px
phone screen and leaving ~135px for content. Now:

- Sidebar becomes a slide-in drawer on <lg, static on ≥lg. Controlled
  via a drawer-open prop wired from the shell; transform + duration
  feels native on touch devices.
- Backdrop overlay closes it on tap; Escape closes it; body scroll
  locks while open; route changes auto-close it.
- Header gains a 44×44 hamburger button on <lg that opens the drawer,
  hidden on ≥lg. Title shrinks slightly on narrow widths so the
  hamburger + avatar + sign-out row never wraps.
- Nav rows are taller (py-2.5) on mobile for comfortable touch
  targets, slim (py-2) back on desktop.
- Main content padding is p-4 on mobile, p-6 on desktop so cards
  have room to breathe without shouldering into the viewport edge.

No desktop changes visible beyond tighter padding on very narrow
lg breakpoints (main content width is unchanged).

Next PRs in this series will adapt:
- Inbox (3-pane → single-pane with back)
- Pipelines (kanban snap-scroll)
- Automation builder (card widths + bottom-sheet edit)
- Tables + forms polish

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:39:08 +03:00

76 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { usePathname } from "next/navigation";
import { useAuth } from "@/hooks/use-auth";
import { LogOut, Menu } from "lucide-react";
const pageTitles: Record<string, string> = {
"/dashboard": "Dashboard",
"/inbox": "Inbox",
"/contacts": "Contacts",
"/pipelines": "Pipelines",
"/broadcasts": "Broadcasts",
"/automations": "Automations",
"/settings": "Settings",
};
function getPageTitle(pathname: string): string {
if (pageTitles[pathname]) return pageTitles[pathname];
const match = Object.entries(pageTitles).find(([path]) =>
pathname.startsWith(path),
);
return match ? match[1] : "Dashboard";
}
interface HeaderProps {
/** Wired to the shell's drawer state. Used only on mobile — the
* hamburger button is hidden on lg+. */
onOpenSidebar?: () => void;
}
export function Header({ onOpenSidebar }: HeaderProps) {
const pathname = usePathname();
const { profile, signOut } = useAuth();
const title = getPageTitle(pathname);
return (
<header className="flex h-14 shrink-0 items-center justify-between gap-3 border-b border-slate-800 bg-slate-950 px-4 lg:px-6">
<div className="flex min-w-0 items-center gap-2">
{/* Hamburger — mobile only. 44×44 hit target per Apple HIG. */}
<button
type="button"
onClick={onOpenSidebar}
aria-label="Open menu"
className="flex h-10 w-10 items-center justify-center rounded-md text-slate-300 transition-colors hover:bg-slate-800 hover:text-white lg:hidden"
>
<Menu className="h-5 w-5" />
</button>
<h1 className="truncate text-base font-semibold text-white sm:text-lg">
{title}
</h1>
</div>
<div className="flex items-center gap-2 sm:gap-3">
<div className="flex items-center gap-2 sm:gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-emerald-500/10 text-sm font-medium text-emerald-500">
{profile?.full_name?.charAt(0)?.toUpperCase() ?? "U"}
</div>
<div className="hidden sm:block">
<p className="text-sm font-medium text-white">
{profile?.full_name ?? "User"}
</p>
</div>
</div>
<button
onClick={signOut}
className="flex h-10 w-10 items-center justify-center rounded-md text-slate-400 transition-colors hover:bg-slate-800 hover:text-white"
title="Sign out"
aria-label="Sign out"
>
<LogOut className="h-4 w-4" />
</button>
</div>
</header>
);
}