Leon4gr45/builder
0
1'use client';2 3import React, { useState } from 'react';4import { Button } from '@/components/ui/button';5import { Badge } from '@/components/ui/badge';6import { Logo } from '@/components/ui/logo';7import { ChevronDown, ChevronUp, Menu } from 'lucide-react';8 9export interface HeaderAction {10 id: string;11 label: string;12 icon?: React.ComponentType<{ className?: string }>;13 onClick: () => void;14 variant?: 'default' | 'outline' | 'ghost' | 'secondary';15 size?: 'default' | 'sm' | 'lg' | 'icon';16 disabled?: boolean;17 content?: React.ReactNode;18 dataTourId?: string;19}20 21export interface ViewTab {22 id: string;23 label: string;24 icon?: React.ComponentType<{ className?: string }>;25}26 27interface AppHeaderProps {28 title?: string;29 subtitle?: string;30 badge?: string;31 onLogoClick?: () => void;32 actions?: HeaderAction[];33 mobileMenuContent?: React.ReactNode;34 desktopOnlyContent?: React.ReactNode; // Content shown only on desktop35 className?: string;36 leftText?: React.ReactNode; // Text to show next to logo on desktop, centered on mobile37 leftSubtext?: string; // Secondary line below leftText on mobile (e.g. active panel name)38 mobileVisibleActions?: string[]; // Action IDs to show outside dropdown on mobile39 viewTabs?: ViewTab[]; // View tabs for switching between sections40 activeViewTab?: string; // Currently active view tab41 onViewTabChange?: (tabId: string) => void; // Callback when view tab changes42 hideLogo?: boolean; // Hide the logo button (for use with sidebar)43 showMobileMenu?: boolean; // Show hamburger menu button on mobile (for sidebar)44 onMobileMenuClick?: () => void; // Callback when hamburger is clicked45 hideActionsOnMobile?: boolean; // Hide all actions on mobile (actions in sidebar instead)46 pageName?: string; // Page name to show in center on mobile (when showMobileMenu is true)47}48 49export function AppHeader({50 title,51 subtitle,52 badge,53 onLogoClick,54 actions = [],55 mobileMenuContent,56 desktopOnlyContent,57 className = '',58 leftText,59 leftSubtext,60 mobileVisibleActions = [],61 viewTabs,62 activeViewTab,63 onViewTabChange,64 hideLogo = false,65 showMobileMenu = false,66 onMobileMenuClick,67 hideActionsOnMobile = false,68 pageName69}: AppHeaderProps) {70 const [mobileMenuOpen, setMobileMenuOpen] = useState(false);71 72 // Split actions into mobile-visible and dropdown-only73 const mobileVisibleActionsSet = new Set(mobileVisibleActions);74 const visibleOnMobile = hideActionsOnMobile ? [] : actions.filter(a => mobileVisibleActionsSet.has(a.id));75 const dropdownOnlyActions = hideActionsOnMobile ? [] : actions.filter(a => !mobileVisibleActionsSet.has(a.id));76 77 return (78 <div className={`border-b bg-card shadow-sm relative z-20 ${className}`}>79 <div className="px-3 py-2 flex items-center justify-between">80 {/* Left side - Logo + pageName (mobile when showMobileMenu is true) */}81 {showMobileMenu && (82 <div className="md:hidden flex items-center gap-3">83 <Logo width={24} height={24} />84 {pageName && <span className="text-sm font-semibold">{pageName}</span>}85 </div>86 )}87 88 {/* Left side - Logo + text (desktop), empty (mobile with sidebar) */}89 {!hideLogo && !showMobileMenu && (90 <button91 onClick={onLogoClick}92 className="flex items-center gap-2 p-1 pr-2 hover:ring-1 hover:ring-border rounded-sm transition-all"93 >94 <Logo width={24} height={24} />95 {leftText && (96 <div className="flex flex-col items-start min-w-0">97 <span className="font-semibold text-sm md:text-lg truncate max-w-[160px] md:max-w-none">{leftText}</span>98 {leftSubtext && <span className="text-xs text-muted-foreground md:hidden">{leftSubtext}</span>}99 </div>100 )}101 </button>102 )}103 104 {/* Center - View tabs or leftText/title (desktop only now) */}105 <div className="flex items-center gap-2 flex-1 justify-center md:justify-start md:ml-6">106 {viewTabs && viewTabs.length > 0 ? (107 /* Show view tabs as pill toggle */108 <div className="flex border rounded-full">109 {viewTabs.map((tab, index) => (110 <Button111 key={tab.id}112 variant={activeViewTab === tab.id ? 'secondary' : 'ghost'}113 size="sm"114 onClick={() => onViewTabChange?.(tab.id)}115 className={`gap-2 ${116 index === 0 ? 'rounded-r-none rounded-l-full' :117 index === viewTabs.length - 1 ? 'rounded-l-none rounded-r-full' :118 'rounded-none'119 }`}120 >121 {tab.icon && <tab.icon className="h-4 w-4" />}122 {tab.label}123 </Button>124 ))}125 </div>126 ) : title ? (127 <>128 {title && <h1 className="text-lg md:text-xl font-semibold">{title}</h1>}129 {badge && <Badge variant="secondary">{badge}</Badge>}130 </>131 ) : null}132 </div>133 134 {/* Desktop - Show subtitle only if no leftText and no center title */}135 {!leftText && !title && subtitle && (136 <div className="hidden md:flex items-center flex-1 ml-6">137 <span className="text-sm text-muted-foreground">{subtitle}</span>138 </div>139 )}140 141 {/* Right side controls */}142 <div className="flex items-center gap-2">143 {/* Desktop actions */}144 <div className="hidden md:flex items-center gap-2">145 {actions.map((action) => (146 action.content ? (147 <div key={action.id}>{action.content}</div>148 ) : (149 <Button150 key={action.id}151 variant={action.variant || 'outline'}152 size={action.size || 'sm'}153 onClick={action.onClick}154 disabled={action.disabled}155 className="justify-start"156 data-tour-id={action.dataTourId}157 >158 {action.icon && <action.icon className="h-4 w-4 mr-2" />}159 {action.label}160 </Button>161 )162 ))}163 {desktopOnlyContent}164 </div>165 166 {/* Mobile visible actions */}167 <div className="md:hidden flex items-center gap-2">168 {visibleOnMobile.map((action) => (169 action.content ? (170 <div key={action.id}>{action.content}</div>171 ) : (172 <Button173 key={action.id}174 variant={action.variant || 'outline'}175 size={action.size || 'sm'}176 onClick={action.onClick}177 disabled={action.disabled}178 className="h-8 px-3"179 data-tour-id={action.dataTourId}180 >181 {action.icon && <action.icon className="h-4 w-4 mr-2" />}182 {action.label}183 </Button>184 )185 ))}186 </div>187 188 {/* Mobile menu toggle */}189 {(dropdownOnlyActions.length > 0 || mobileMenuContent) && (190 <Button191 variant="ghost"192 size="icon"193 onClick={() => setMobileMenuOpen(!mobileMenuOpen)}194 className="h-8 w-8 md:hidden"195 >196 {mobileMenuOpen ? (197 <ChevronUp className="h-4 w-4" />198 ) : (199 <ChevronDown className="h-4 w-4" />200 )}201 </Button>202 )}203 204 {/* Mobile hamburger menu (on right side) */}205 {showMobileMenu && (206 <Button207 variant="ghost"208 size="icon"209 onClick={onMobileMenuClick}210 className="md:hidden h-8 w-8"211 >212 <Menu className="h-5 w-5" />213 </Button>214 )}215 </div>216 </div>217 218 {/* Mobile dropdown menu */}219 {mobileMenuOpen && (dropdownOnlyActions.length > 0 || mobileMenuContent) && (220 <div className="md:hidden border-t bg-muted/30 px-4 py-4 space-y-3">221 {/* Show subtitle on mobile if no leftText and no center title */}222 {!leftText && !title && subtitle && (223 <div className="pb-2 border-b border-border/50">224 <p className="text-sm text-muted-foreground">{subtitle}</p>225 </div>226 )}227 228 {/* Mobile dropdown-only actions */}229 {dropdownOnlyActions.length > 0 && (230 <div className="space-y-2">231 {dropdownOnlyActions.map((action) => (232 action.content ? (233 <div key={action.id}>{action.content}</div>234 ) : (235 <Button236 key={action.id}237 variant={action.variant || 'outline'}238 size={action.size || 'sm'}239 onClick={() => {240 action.onClick();241 setMobileMenuOpen(false);242 }}243 disabled={action.disabled}244 className="w-full justify-start"245 data-tour-id={action.dataTourId}246 >247 {action.icon && <action.icon className="h-4 w-4 mr-2" />}248 {action.label}249 </Button>250 )251 ))}252 </div>253 )}254 255 {/* Custom mobile menu content */}256 {mobileMenuContent && (257 <div className="pt-2 border-t border-border/50">258 {mobileMenuContent}259 </div>260 )}261 </div>262 )}263 </div>264 );265}