NeonClary's picture
Deploy brand mark, logo palette, goal-aware starters, and carousel fit.
ffcad5d verified
Raw
History Blame Contribute Delete
4.33 kB
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;