Spaces:
Running
Running
| import React from 'react'; | |
| import { Menu } from 'lucide-react'; | |
| import { useAppConfig } from '../contexts/AppConfigContext'; | |
| /** | |
| * Shared floating header used on every page so the app feels like one surface. | |
| * | |
| * Props: | |
| * currentPage: 'home' | 'chat' | 'canvas' | 'journey' | 'canvas-<subview>' | |
| * onNavigateToHome, onNavigateToChat, onNavigateToCanvas, onNavigateToJourney | |
| * (onNavigateToCanvas may receive 'workspace' | 'deliverables' to deep-link a view; | |
| * 'journey' is handled via onNavigateToJourney when provided) | |
| * onMobileMenu?: () => void — when present, shows the mobile menu button | |
| * children?: ReactNode — extra controls slotted in header-right | |
| */ | |
| const AppHeader = ({ | |
| currentPage = 'home', | |
| onNavigateToHome, | |
| onNavigateToChat, | |
| onNavigateToCanvas, | |
| onNavigateToJourney, | |
| onMobileMenu, | |
| children, | |
| }) => { | |
| const { config } = useAppConfig(); | |
| const goToCanvas = (view) => { | |
| if (view === 'journey') { | |
| if (onNavigateToJourney) onNavigateToJourney(); | |
| else if (onNavigateToCanvas) onNavigateToCanvas('journey'); | |
| return; | |
| } | |
| if (onNavigateToCanvas) onNavigateToCanvas(view); | |
| }; | |
| // Accept either 'canvas' (all canvas tabs highlight equally) or a more specific | |
| // 'canvas-<subview>' from CanvasPage so only the active one highlights. | |
| const isOnHome = currentPage === 'home'; | |
| const isOnChat = currentPage === 'chat'; | |
| const isOnJourney = currentPage === 'journey'; | |
| const isOnCanvas = currentPage === 'canvas' || currentPage.startsWith('canvas-'); | |
| const canvasSub = currentPage.startsWith('canvas-') ? currentPage.slice(7) : null; | |
| const tabActive = (sub) => isOnCanvas && (canvasSub === null ? false : canvasSub === sub); | |
| return ( | |
| <header className="floating-header app-header"> | |
| <div className="header-left"> | |
| {onMobileMenu && ( | |
| <button className="mobile-menu-button" onClick={onMobileMenu}> | |
| <Menu size={20} /> | |
| </button> | |
| )} | |
| <button | |
| className="modern-home-btn" | |
| onClick={onNavigateToHome} | |
| title={config?.app?.title || 'Cybersecurity Advisor'} | |
| disabled={isOnHome} | |
| aria-disabled={isOnHome} | |
| aria-label={config?.app?.title || 'Cybersecurity Advisor'} | |
| > | |
| <img | |
| src="/brand/cybersecurity-advisor-mark.png" | |
| alt="" | |
| className="header-brand-mark" | |
| /> | |
| </button> | |
| <div className="header-brand"> | |
| <div className="brand-text"> | |
| <h1>{config?.app?.title || 'Advisory'}</h1> | |
| <p>{config?.app?.subtitle || 'AI-Powered Guidance'}</p> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Hide the view pill bar on the home page — home is a landing page, | |
| not part of the chat ↔ canvas surface. */} | |
| {!isOnHome && ( | |
| <div className="canvas-tabs chat-view-tabs"> | |
| <button className={`tab ${isOnChat ? 'active' : ''}`} onClick={onNavigateToChat}>Chat</button> | |
| <button className={`tab ${isOnJourney ? 'active' : ''}`} onClick={() => goToCanvas('journey')}>Journey</button> | |
| <button className={`tab ${tabActive('workspace') ? 'active' : ''}`} onClick={() => goToCanvas('workspace')}>Workspace</button> | |
| <button className={`tab ${tabActive('deliverables') ? 'active' : ''}`} onClick={() => goToCanvas('deliverables')}>Documents</button> | |
| </div> | |
| )} | |
| {/* Compact mobile dropdown — appears in place of the pill bar at narrow widths */} | |
| {!isOnHome && ( | |
| <select | |
| className="canvas-tabs-mobile" | |
| value={isOnChat ? 'chat' : (isOnJourney ? 'journey' : (canvasSub || 'workspace'))} | |
| onChange={(e) => { | |
| const v = e.target.value; | |
| if (v === 'chat') onNavigateToChat(); | |
| else goToCanvas(v); | |
| }} | |
| > | |
| <option value="chat">Chat</option> | |
| <option value="journey">Journey</option> | |
| <option value="workspace">Workspace</option> | |
| <option value="deliverables">Documents</option> | |
| </select> | |
| )} | |
| <div className="header-right"> | |
| {children} | |
| </div> | |
| </header> | |
| ); | |
| }; | |
| export default AppHeader; | |