css-fix-test-1770192445/packages/layout/src/Header.tsx
jordan 6069570da0
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/manual/woodpecker Pipeline was successful
Initialize project from skeleton template
2026-02-04 08:07:26 +00:00

119 lines
3.4 KiB
TypeScript

import * as React from 'react';
import { cn, Button, Input, Search } from '@css-fix-test-1770192445/ui';
import { Bell, Menu } from 'lucide-react';
export interface HeaderProps {
/** Title to display in the header */
title?: string;
/** Breadcrumb or secondary navigation element */
breadcrumb?: React.ReactNode;
/** Whether to show the search input */
showSearch?: boolean;
/** Placeholder text for search input */
searchPlaceholder?: string;
/** Search input change handler */
onSearch?: (value: string) => void;
/** Custom actions to display on the right side */
actions?: React.ReactNode;
/** User menu or avatar element */
userMenu?: React.ReactNode;
/** Mobile menu toggle handler */
onMenuToggle?: () => void;
/** Whether to show the menu toggle button (for mobile) */
showMenuToggle?: boolean;
/** Additional class names */
className?: string;
}
/**
* Header provides the top navigation bar for dashboard applications.
* Supports title, breadcrumbs, search, notifications, and user menu.
*
* @example
* <Header
* title="Dashboard"
* showSearch
* onSearch={(value) => console.log(value)}
* userMenu={<UserDropdown />}
* />
*/
export function Header({
title,
breadcrumb,
showSearch = false,
searchPlaceholder = 'Search...',
onSearch,
actions,
userMenu,
onMenuToggle,
showMenuToggle = false,
className,
}: HeaderProps) {
const [searchValue, setSearchValue] = React.useState('');
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value);
onSearch?.(e.target.value);
};
return (
<div className={cn('flex items-center justify-between w-full px-6', className)}>
{/* Left section */}
<div className="flex items-center gap-4">
{showMenuToggle && (
<Button
variant="ghost"
size="icon"
onClick={onMenuToggle}
className="lg:hidden"
>
<Menu className="h-5 w-5" />
<span className="sr-only">Toggle menu</span>
</Button>
)}
{breadcrumb ? (
<div className="flex items-center">{breadcrumb}</div>
) : title ? (
<h1 className="text-lg font-semibold">{title}</h1>
) : null}
</div>
{/* Center section - Search */}
{showSearch && (
<div className="hidden md:flex flex-1 max-w-md mx-8">
<div className="relative w-full">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--text-muted)]" />
<Input
type="search"
placeholder={searchPlaceholder}
value={searchValue}
onChange={handleSearchChange}
className="pl-9 bg-[var(--surface-50)] border-[var(--border-muted)]"
/>
</div>
</div>
)}
{/* Right section */}
<div className="flex items-center gap-2">
{actions}
{/* Notifications */}
<Button variant="ghost" size="icon" className="relative">
<Bell className="h-5 w-5" />
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-[var(--error)]" />
<span className="sr-only">Notifications</span>
</Button>
{/* User menu */}
{userMenu && (
<div className="ml-2 border-l border-[var(--border-muted)] pl-4">
{userMenu}
</div>
)}
</div>
</div>
);
}