All files / src/components/layout top-bar.tsx

100% Statements 1/1
100% Branches 7/7
100% Functions 1/1
100% Lines 1/1

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79                                                                                      57x                                                                      
import { Menu } from "lucide-react";
import type { ReactNode, RefObject } from "react";
import { cn } from "../../lib/utils";
import { LogoIcon } from "../ui/logo-icon";
import { ReleaseBadge } from "../ui/release-badge";
 
/**
 * The 48px top bar (plan §1.2 / `.context/mockups/shell.html`):
 *
 *     ▣ INTERIORING  v2.31              ⌕        ⚲ 3     ◑     SR ▾
 *
 * A hairline under it. No shadow, no second row — the shell's whole vertical
 * budget above the content is three rem.
 *
 * The mockup's global search is NOT here: there is no cross-entity search in
 * the pro portal to wire it to (the only search surface is the admin page at
 * `/admin/search`). A box that returns nothing is worse than no box, so the
 * slot is left to the `children` the layouts actually have.
 */
export type TopBarProps = {
	/** Opens the rail drawer. Omit on surfaces with no drawer. */
	onOpenNav?: () => void;
	navOpen?: boolean;
	menuButtonRef?: RefObject<HTMLButtonElement | null>;
	/**
	 * Where the hamburger shows. Defaults to "below the static rail". The pro
	 * shell narrows it to tablets, because phones navigate from the bottom.
	 */
	menuClassName?: string;
	/** A `.label` beside the brand: "Admin". */
	label?: string;
	/** The right-hand controls: bell, theme, whatever the surface has. */
	children?: ReactNode;
};
 
export function TopBar({
	onOpenNav,
	navOpen,
	menuButtonRef,
	menuClassName = "lg:hidden",
	label,
	children,
}: TopBarProps) {
	return (
		<div className="flex h-12 items-center gap-2 border-b border-border-default bg-background-elevated px-3 sm:px-4">
			{onOpenNav && (
				<button
					ref={menuButtonRef}
					type="button"
					onClick={onOpenNav}
					aria-label="Open navigation"
					aria-expanded={navOpen ?? false}
					className={cn(
						"-ml-1 flex size-8 items-center justify-center text-foreground-muted transition-colors hover:text-foreground-default pointer-coarse:size-11",
						menuClassName,
					)}
				>
					<Menu className="size-5" aria-hidden="true" />
				</button>
			)}
 
			<span className="flex min-w-0 items-center gap-1.5">
				<LogoIcon size={24} className="flex-none text-foreground-default" />
				<span className="truncate text-sm font-semibold text-foreground-default">
					Interioring
				</span>
				{label && (
					<span className="text-xs font-semibold uppercase tracking-wide text-foreground-muted">
						{label}
					</span>
				)}
				<ReleaseBadge />
			</span>
 
			<span className="ml-auto flex items-center gap-1">{children}</span>
		</div>
	);
}