{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "app-header", "title": "App Header", "description": "Authenticated application header with mobile menu, user dropdown, notifications, and command palette search", "dependencies": [ "lucide-react" ], "registryDependencies": [ "button", "sheet", "dropdown-menu", "command", "mini-card" ], "files": [ { "path": "registry/blocks/app-header/page.tsx", "content": "import { AppHeader } from \"@/registry/blocks/app-header/components/app-header\"\n\nexport default function AppHeaderDemo() {\n return (\n
\n \n\n
\n

\n App Header Demo\n

\n

\n This is a demo of the App Header component with default configuration.\n The header includes a hamburger menu, logo, search, notifications, and user menu.\n Press \u2318K or Ctrl+K to open the command palette.\n

\n\n
\n

\n Configurable Props\n

\n
    \n
  • agentName - Display name in user menu
  • \n
  • agentRole - Role displayed under name
  • \n
  • title - Header title text
  • \n
  • logoSrc - Path to logo image
  • \n
  • homeHref - Home link destination
  • \n
  • navItems - Array of navigation items
  • \n
  • footerItems - Array of footer nav items
  • \n
  • logoutAction - Server action for logout
  • \n
  • showNotifications - Show/hide notifications button
  • \n
  • showSearch - Show/hide search button
  • \n
  • commandGroups - Command palette groups
  • \n
\n
\n
\n
\n )\n}\n", "type": "registry:page", "target": "app/app-header-demo/page.tsx" }, { "path": "registry/blocks/app-header/components/app-header.tsx", "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport Link from \"next/link\"\nimport { usePathname, useRouter } from \"next/navigation\"\nimport {\n Menu,\n Search,\n BellIcon,\n LogOut,\n User,\n Settings,\n ListTodo,\n type LucideIcon,\n} from \"lucide-react\"\nimport { Button } from \"@/registry/ui/button\"\nimport {\n Sheet,\n SheetContent,\n SheetClose,\n} from \"@/registry/ui/sheet\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/registry/ui/dropdown-menu\"\nimport { MiniCard } from \"@/registry/ui/mini-card\"\nimport {\n CommandDialog,\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from \"@/registry/ui/command\"\n\n// Types\nexport interface NavItem {\n title: string\n url: string\n icon?: LucideIcon\n}\n\nexport interface CommandItemType {\n label: string\n icon?: LucideIcon\n href?: string\n action?: () => void\n}\n\nexport interface CommandGroupType {\n heading: string\n items: CommandItemType[]\n}\n\nconst DEFAULT_NAV_ITEMS: NavItem[] = [\n { title: \"Queue\", url: \"/queue\" },\n]\n\nconst DEFAULT_FOOTER_ITEMS: NavItem[] = []\n\n// Default logout stub - replace with your auth implementation\nasync function defaultLogout(): Promise {\n console.warn(\"AppHeader: No logoutAction provided. Implement your own logout logic.\")\n}\n\ninterface AppHeaderProps {\n agentName?: string\n agentRole?: string\n title?: string\n logoSrc?: string\n logoAlt?: string\n homeHref?: string\n navItems?: NavItem[]\n footerItems?: NavItem[]\n logoutAction?: () => Promise\n showNotifications?: boolean\n showSearch?: boolean\n commandGroups?: CommandGroupType[]\n}\n\nexport function AppHeader({\n agentName,\n agentRole,\n title = \"TTB Label Verification\",\n logoSrc = \"/logo/app-seal/treasury.png\",\n logoAlt = \"Treasury\",\n homeHref = \"/queue\",\n navItems = DEFAULT_NAV_ITEMS,\n footerItems = DEFAULT_FOOTER_ITEMS,\n logoutAction = defaultLogout,\n showNotifications = true,\n showSearch = true,\n commandGroups,\n}: AppHeaderProps) {\n const [menuOpen, setMenuOpen] = useState(false)\n const [commandOpen, setCommandOpen] = useState(false)\n const pathname = usePathname()\n const router = useRouter()\n const logoutFormRef = useRef(null)\n\n useEffect(() => {\n const down = (e: KeyboardEvent) => {\n if (e.key === \"k\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n setCommandOpen((open) => !open)\n }\n }\n\n document.addEventListener(\"keydown\", down)\n return () => document.removeEventListener(\"keydown\", down)\n }, [])\n\n const runCommand = (command: () => void) => {\n setCommandOpen(false)\n command()\n }\n\n const submitLogout = () => {\n logoutFormRef.current?.requestSubmit()\n }\n\n // Default command groups if not provided\n const defaultCommandGroups: CommandGroupType[] = [\n {\n heading: \"Navigation\",\n items: [\n { label: \"Queue\", icon: ListTodo, href: \"/queue\" },\n ],\n },\n {\n heading: \"Account\",\n items: [\n { label: \"Settings\", icon: Settings, href: \"/settings\" },\n { label: \"Log out\", icon: LogOut, action: submitLogout },\n ],\n },\n ]\n\n const groups = commandGroups ?? defaultCommandGroups\n\n return (\n
\n
\n {/* Left side */}\n
\n setMenuOpen(true)}\n className=\"text-treasury-paper hover:bg-treasury-primary/50 h-auto w-auto p-1\"\n aria-label=\"Open menu\"\n >\n \n \n\n \n \n \n {title}\n \n \n
\n\n {/* Right side */}\n
\n {showSearch && (\n setCommandOpen(true)}\n className=\"text-treasury-paper hover:bg-treasury-primary/50\"\n aria-label=\"Search (\u2318K)\"\n >\n \n \n )}\n\n {showNotifications && (\n \n \n \n )}\n\n \n \n \n \n \n \n \n {agentName ? (\n <>\n \n
\n {agentName}\n
\n {agentRole ? (\n
\n {agentRole}\n
\n ) : null}\n
\n \n \n ) : null}\n \n \n \n \n Settings\n \n \n \n \n
\n \n \n \n
\n
\n
\n
\n
\n\n \n \n \n\n \n \n \n\n {showSearch && (\n \n \n \n \n No results found.\n\n {groups.map((group, groupIndex) => (\n
\n {groupIndex > 0 && }\n \n {group.items.map((item) => (\n \n runCommand(() => {\n if (item.action) {\n item.action()\n } else if (item.href) {\n router.push(item.href)\n }\n })\n }\n >\n {item.icon && }\n {item.label}\n \n ))}\n \n
\n ))}\n
\n
\n
\n )}\n\n
\n \n
\n
\n )\n}\n", "type": "registry:component" } ], "type": "registry:block" }