docuflow / src /components /layout /Header.tsx
Joedroid's picture
feat: make realtime AI tools hideable, add layout toggle, and ensure dropdowns are fully theme aware
6130a35
Raw
History Blame Contribute Delete
27.6 kB
import React, { useState } from "react";
import {
Menu,
Printer,
Save,
Type,
Settings,
X,
Layers,
User
} from "lucide-react";
import { AuthUser } from "../../types";
interface HeaderProps {
user: AuthUser;
isSidebarOpen: boolean;
setIsSidebarOpen: (open: boolean) => void;
activeLayoutPreset: "default" | "writer" | "collab";
editorFontFamily: "sans" | "serif" | "mono";
setEditorFontFamily: (font: "sans" | "serif" | "mono") => void;
autoSaveEnabled: boolean;
setAutoSaveEnabled: (val: boolean) => void;
handlePrintPdf: () => void;
handleManualSave: () => void;
resolvedTheme: "dark" | "light";
themeMode: "system" | "dark" | "light";
setThemeMode: (theme: "system" | "dark" | "light") => void;
documentInputTheme: "modern" | "terminal" | "notebook" | "ivory" | "monokai" | "darcula" | "solarized-dark" | "solarized-light";
setDocumentInputTheme: (theme: any) => void;
documentExportTheme: "corporate" | "tech" | "academic" | "sunset" | "midnight";
setDocumentExportTheme: (theme: any) => void;
printIncludeCoverPage: boolean;
setPrintIncludeCoverPage: (inc: boolean) => void;
printIncludeQRCode: boolean;
setPrintIncludeQRCode: (inc: boolean) => void;
printWatermarkType: "none" | "confidential" | "draft" | "custom";
setPrintWatermarkType: (type: any) => void;
printWatermarkText: string;
setPrintWatermarkText: (txt: string) => void;
prefSyncStatus: "idle" | "syncing" | "synced" | "error";
syncPreferencesToServer: () => void;
handleApplyPreset: (preset: "default" | "writer" | "collab") => void;
showFolderSidebar: boolean;
setShowFolderSidebar: (show: boolean) => void;
showAIPanel: boolean;
setShowAIPanel: (show: boolean) => void;
showLogs: boolean;
setShowLogs: (show: boolean) => void;
showPreview: boolean;
setShowPreview: (show: boolean) => void;
showAiTools: boolean;
setShowAiTools: (show: boolean) => void;
addLog: (text: string, type: "success" | "info" | "sync" | "error") => void;
}
export default function Header({
user,
isSidebarOpen,
setIsSidebarOpen,
activeLayoutPreset,
editorFontFamily,
setEditorFontFamily,
autoSaveEnabled,
setAutoSaveEnabled,
handlePrintPdf,
handleManualSave,
resolvedTheme,
themeMode,
setThemeMode,
documentInputTheme,
setDocumentInputTheme,
documentExportTheme,
setDocumentExportTheme,
printIncludeCoverPage,
setPrintIncludeCoverPage,
printIncludeQRCode,
setPrintIncludeQRCode,
printWatermarkType,
setPrintWatermarkType,
printWatermarkText,
setPrintWatermarkText,
prefSyncStatus,
syncPreferencesToServer,
handleApplyPreset,
showFolderSidebar,
setShowFolderSidebar,
showAIPanel,
setShowAIPanel,
showLogs,
setShowLogs,
showPreview,
setShowPreview,
showAiTools,
setShowAiTools,
addLog
}: HeaderProps) {
const [isPreferencesDropdownOpen, setIsPreferencesDropdownOpen] = useState(false);
return (
<header className="h-14 bg-[#1A1D23] border-b border-slate-800 sticky top-0 z-30 flex items-center flex-shrink-0">
<div className="w-full px-4 sm:px-6 flex items-center justify-between h-full gap-3">
{/* Logo, Sidebar Toggle Button & Workspace Title */}
<div className="flex items-center space-x-3">
<button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="p-1 px-2.5 bg-[#15181E] hover:bg-slate-800/80 text-slate-300 hover:text-white rounded-lg border border-slate-800 text-xs font-semibold select-none transition cursor-pointer font-sans flex items-center gap-1.5"
title="Toggle Global Navigation Sidebar"
>
<Menu className="h-4 w-4 text-blue-500" />
<span className="hidden sm:inline font-mono">Menu</span>
</button>
<div className="h-5 w-px bg-slate-800 hidden md:block"></div>
<div className="flex items-center space-x-2">
<div className="w-7 h-7 bg-blue-600 rounded-lg flex items-center justify-center">
<svg className="w-4.5 h-4.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
</div>
<span className="font-bold text-white tracking-tight text-xs md:text-sm flex items-baseline gap-1">
DOCUFLOW <span className="text-blue-550 text-[10px] font-mono font-bold">PRO</span>
</span>
</div>
<div className="h-5 w-px bg-slate-800 hidden lg:block"></div>
<span className="text-[10px] text-slate-500 font-mono tracking-wider uppercase hidden lg:inline">Workspace Hub</span>
</div>
{/* Minimal navbar right actions: Operator Preferences & Tier Status */}
<div className="flex items-center space-x-3 relative">
{/* Prominent Export as PDF Button */}
<button
onClick={handlePrintPdf}
className="flex items-center gap-1.5 px-3 py-1.5 bg-gradient-to-r from-indigo-600 to-blue-600 hover:from-indigo-500 hover:to-blue-500 text-white rounded-lg text-xs font-semibold select-none shadow-md shadow-indigo-600/15 hover:shadow-indigo-600/25 active:scale-95 transition cursor-pointer font-sans border border-indigo-500/35"
title="Print preview or save your document directly as high-fidelity custom PDF (Alt+P)"
>
<Printer className="h-3.5 w-3.5 text-indigo-100" />
<span className="hidden sm:inline font-mono">Export as PDF</span>
</button>
{/* Manual Save Button - visible when Auto-save is disabled */}
{!autoSaveEnabled && (
<button
onClick={handleManualSave}
className="flex items-center gap-1.5 px-3 py-1.5 bg-emerald-600/10 hover:bg-emerald-600/25 border border-emerald-500/30 hover:border-emerald-550 text-emerald-400 hover:text-emerald-300 rounded-lg text-xs font-semibold select-none transition cursor-pointer font-sans duration-150 shadow-md animate-pulse shrink-0"
title="Save current progress manually to local storage & cloud (Ctrl+S)"
>
<Save className="h-3.5 w-3.5" />
<span className="hidden sm:inline font-mono">Save Draft</span>
</button>
)}
{/* Font Family Selector Dropdown for Workspace Editor */}
<div className="hidden sm:flex items-center gap-1.5 px-3 py-1.5 bg-[#15181E] border border-slate-800 rounded-lg text-xs font-semibold select-none transition font-sans hover:border-slate-700/80">
<Type className="h-3.5 w-3.5 text-blue-500" />
<select
value={editorFontFamily}
onChange={(e) => {
const val = e.target.value as "sans" | "serif" | "mono";
setEditorFontFamily(val);
localStorage.setItem("workspace_pref_editorFontFamily", val);
addLog(`Workspace editor font family adjusted to ${val.toUpperCase()}`, "info");
}}
className="bg-transparent text-slate-300 hover:text-white text-xs font-mono focus:outline-none cursor-pointer border-none p-0 pr-1.5"
title="Select workspace editor font family"
>
<option value="mono" className="bg-[#15181E] text-slate-300">Monospace</option>
<option value="sans" className="bg-[#15181E] text-slate-300">Sans-Serif</option>
<option value="serif" className="bg-[#15181E] text-slate-300">Serif</option>
</select>
</div>
{/* Preferences & Layout Configuration Dropdown Trigger */}
<button
onClick={() => setIsPreferencesDropdownOpen(!isPreferencesDropdownOpen)}
className="flex items-center gap-1.5 px-3 py-1.5 bg-[#15181E] border border-slate-855 hover:bg-slate-800 text-slate-305 hover:text-white rounded-lg text-xs font-semibold select-none transition cursor-pointer font-sans"
title="Configure application layout, theme parameters, input writing pads and document export styles"
>
<Settings className="h-3.5 w-3.5 text-blue-500" />
<span className="hidden md:inline font-mono">Preferences</span>
<span className="text-[10px] bg-blue-600/20 text-blue-400 rounded-md px-1 py-0.2 font-mono">
{activeLayoutPreset.toUpperCase()}
</span>
</button>
{/* Dropdown menu modal popover overlay */}
{isPreferencesDropdownOpen && (
<div className="absolute right-0 top-10 bg-[#15181E] border border-[#1E293B] rounded-xl shadow-2xl p-4 w-[290px] md:w-[350px] z-50 text-left space-y-4 animate-fadeIn">
<div className="flex items-center justify-between border-b border-slate-800 pb-2">
<div className="flex items-center gap-1.5">
<Layers className="h-4 w-4 text-emerald-400" />
<span className="text-xs font-bold text-white uppercase tracking-wider font-mono">Workspace Settings</span>
</div>
<button
onClick={() => setIsPreferencesDropdownOpen(false)}
className="text-slate-500 hover:text-white transition p-1 hover:bg-slate-800 rounded cursor-pointer"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
{/* Theme options */}
<div className="space-y-1.5">
<span className="block text-[10px] uppercase font-semibold text-slate-505 tracking-wider font-mono">App Theme</span>
<div className="grid grid-cols-3 gap-1">
{(["system", "dark", "light"] as const).map((t) => (
<button
key={t}
type="button"
onClick={() => setThemeMode(t)}
className={`text-[10px] px-2 py-1 rounded border font-mono transition text-center cursor-pointer ${
themeMode === t
? "bg-blue-600/15 border-blue-500 text-white font-semibold"
: "bg-[#0F1115] border-slate-800 text-slate-400 hover:text-white"
}`}
>
{t === "system" ? "πŸ’» OS" : t === "dark" ? "πŸŒ™ Dark" : "β˜€οΈ Light"}
</button>
))}
</div>
</div>
{/* Preset Layout Options */}
<div className="space-y-1.5">
<span className="block text-[10px] uppercase font-semibold text-slate-505 tracking-wider font-mono">Layout Preset</span>
<div className="grid grid-cols-3 gap-1">
{(["default", "writer", "collab"] as const).map((p) => (
<button
key={p}
type="button"
onClick={() => handleApplyPreset(p)}
className={`text-[10px] px-2 py-1 rounded border font-mono transition text-center cursor-pointer ${
activeLayoutPreset === p
? "bg-emerald-600/15 border-emerald-500 text-white font-semibold"
: "bg-[#0F1115] border-slate-800 text-slate-400 hover:text-white"
}`}
>
{p === "default" ? "🏒 Grid" : p === "writer" ? "πŸ“ Writer" : "🀝 Collab"}
</button>
))}
</div>
</div>
{/* Container checklist controls */}
<div className="space-y-1.5 bg-[#0F1115]/60 p-2.5 rounded-lg border border-slate-805">
<span className="block text-[10px] uppercase font-semibold text-slate-505 tracking-wider font-mono">Workspace Layout Toggles</span>
<div className="flex flex-col gap-1.5 pt-1">
<div className="flex items-center justify-between text-[11px] text-slate-300 font-mono hover:bg-slate-800/20 px-1 py-0.5 rounded transition select-none">
<span>πŸ“ File Browser</span>
<button
type="button"
onClick={() => {
setShowFolderSidebar(!showFolderSidebar);
handleApplyPreset("default");
}}
className={`relative inline-flex h-4 w-8 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus:outline-none cursor-pointer ${
showFolderSidebar ? "bg-blue-600" : "bg-slate-700"
}`}
title="Toggle File browser sidebar visibility"
>
<span
className={`inline-block h-2.5 w-2.5 transform rounded-full bg-white transition-transform duration-200 ${
showFolderSidebar ? "translate-x-4.5" : "translate-x-1"
}`}
/>
</button>
</div>
<div className="flex items-center justify-between text-[11px] text-slate-300 font-mono hover:bg-slate-800/20 px-1 py-0.5 rounded transition select-none">
<span>πŸ€– Chat & Co-pilot</span>
<button
type="button"
onClick={() => {
setShowAIPanel(!showAIPanel);
handleApplyPreset("default");
}}
className={`relative inline-flex h-4 w-8 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus:outline-none cursor-pointer ${
showAIPanel ? "bg-blue-600" : "bg-slate-700"
}`}
title="Toggle Document Chat & AI Co-pilot panel visibility"
>
<span
className={`inline-block h-2.5 w-2.5 transform rounded-full bg-white transition-transform duration-200 ${
showAIPanel ? "translate-x-4.5" : "translate-x-1"
}`}
/>
</button>
</div>
<div className="flex items-center justify-between text-[11px] text-slate-300 font-mono hover:bg-slate-800/20 px-1 py-0.5 rounded transition select-none">
<span>πŸ“Š Logs Window</span>
<button
type="button"
onClick={() => {
setShowLogs(!showLogs);
handleApplyPreset("default");
}}
className={`relative inline-flex h-4 w-8 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus:outline-none cursor-pointer ${
showLogs ? "bg-blue-600" : "bg-slate-700"
}`}
title="Toggle Handshake Logs visibility"
>
<span
className={`inline-block h-2.5 w-2.5 transform rounded-full bg-white transition-transform duration-200 ${
showLogs ? "translate-x-4.5" : "translate-x-1"
}`}
/>
</button>
</div>
<div className="flex items-center justify-between text-[11px] text-slate-300 font-mono hover:bg-slate-800/20 px-1 py-0.5 rounded transition select-none">
<span>πŸ‘οΈ Live Preview</span>
<button
type="button"
onClick={() => {
setShowPreview(!showPreview);
handleApplyPreset("default");
}}
className={`relative inline-flex h-4 w-8 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus:outline-none cursor-pointer ${
showPreview ? "bg-blue-600" : "bg-slate-700"
}`}
title="Toggle PDF Document Preview visibility"
>
<span
className={`inline-block h-2.5 w-2.5 transform rounded-full bg-white transition-transform duration-200 ${
showPreview ? "translate-x-4.5" : "translate-x-1"
}`}
/>
</button>
</div>
<div className="flex items-center justify-between text-[11px] text-slate-300 font-mono hover:bg-slate-800/20 px-1 py-0.5 rounded transition select-none">
<span>✨ AI Assistant Tools</span>
<button
type="button"
onClick={() => {
setShowAiTools(!showAiTools);
handleApplyPreset("default");
}}
className={`relative inline-flex h-4 w-8 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus:outline-none cursor-pointer ${
showAiTools ? "bg-blue-600" : "bg-slate-700"
}`}
title="Toggle AI Assistant Tools visibility"
>
<span
className={`inline-block h-2.5 w-2.5 transform rounded-full bg-white transition-transform duration-200 ${
showAiTools ? "translate-x-4.5" : "translate-x-1"
}`}
/>
</button>
</div>
<div className="flex items-center justify-between text-[11px] text-slate-300 font-mono hover:bg-slate-800/20 px-1 py-0.5 rounded transition select-none">
<span>πŸ’Ύ Auto-save</span>
<button
type="button"
onClick={() => {
const nextVal = !autoSaveEnabled;
setAutoSaveEnabled(nextVal);
localStorage.setItem("workspace_pref_autoSave", String(nextVal));
addLog(`LocalStorage Auto-save mechanism ${nextVal ? "ENABLED" : "DISABLED"}.`, nextVal ? "success" : "info");
}}
className={`relative inline-flex h-4 w-8 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus:outline-none cursor-pointer ${
autoSaveEnabled ? "bg-blue-600" : "bg-slate-700"
}`}
title="Toggle automatic progress saving to local storage"
>
<span
className={`inline-block h-2.5 w-2.5 transform rounded-full bg-white transition-transform duration-200 ${
autoSaveEnabled ? "translate-x-4.5" : "translate-x-1"
}`}
/>
</button>
</div>
</div>
</div>
{/* Input Themes selectors */}
<div className="space-y-1.5">
<span className="block text-[10px] uppercase font-semibold text-slate-505 tracking-wider font-mono">Editor Panel Themes</span>
<div className="grid grid-cols-2 gap-1">
{(["modern", "terminal", "notebook", "ivory", "monokai", "darcula", "solarized-dark", "solarized-light"] as const).map((it) => (
<button
key={it}
type="button"
onClick={() => {
setDocumentInputTheme(it);
addLog(`Input workspace theme adjusted to ${it.toUpperCase()}`, "info");
}}
className={`text-[10px] px-2 py-1 rounded border font-mono transition text-center cursor-pointer ${
documentInputTheme === it
? "bg-purple-600/15 border-purple-500 text-white font-bold animate-pulse"
: "bg-[#0F1115] border-slate-800 text-slate-400 hover:text-white"
}`}
>
{it === "modern" ? "πŸ’» Slate" :
it === "terminal" ? "πŸ“Ÿ Terminal" :
it === "notebook" ? "β˜• Journal" :
it === "ivory" ? "πŸ₯› Ivory" :
it === "monokai" ? "🌺 Monokai" :
it === "darcula" ? "πŸ§› Darcula" :
it === "solarized-dark" ? "β˜€οΈ Solar-D" : "🌀️ Solar-L"}
</button>
))}
</div>
</div>
{/* Export Style document themes select */}
<div className="space-y-1.5">
<span className="block text-[10px] uppercase font-semibold text-slate-505 tracking-wider font-mono">PDF Page Themes</span>
<div className="grid grid-cols-2 gap-1">
{(["corporate", "tech", "academic", "sunset", "midnight"] as const).map((et) => (
<button
key={et}
type="button"
onClick={() => setDocumentExportTheme(et)}
className={`text-[10px] px-2 py-1 rounded border font-mono transition text-center cursor-pointer ${
documentExportTheme === et
? "bg-[#6366F1]/15 border-[#6366F1] text-white font-bold"
: "bg-[#0F1115] border-slate-800 text-slate-400 hover:text-white"
}`}
>
{et === "corporate" ? "πŸ‘” Corp" :
et === "tech" ? "🦾 Tech" :
et === "academic" ? "πŸ“š Academ" :
et === "sunset" ? "πŸŒ‡ Sunset" : "πŸŒƒ Midnight"}
</button>
))}
</div>
</div>
{/* Print & PDF Layout Controls */}
<div className="space-y-2 border-t border-slate-900 pt-2.5">
<span className="block text-[10px] uppercase font-semibold text-slate-505 tracking-wider font-mono">Print Layout Addons</span>
<div className="space-y-1.5 text-slate-350 text-[11px] font-sans">
<label className="flex items-center gap-2 cursor-pointer hover:text-white transition select-none">
<input
type="checkbox"
checked={printIncludeCoverPage}
onChange={(e) => setPrintIncludeCoverPage(e.target.checked)}
className="rounded border-slate-800 bg-[#0F1115] text-[#6366F1] focus:ring-0 w-3.5 h-3.5 cursor-pointer"
/>
<span>Render Cover Page first</span>
</label>
<label className="flex items-center gap-2 cursor-pointer hover:text-white transition select-none">
<input
type="checkbox"
checked={printIncludeQRCode}
onChange={(e) => setPrintIncludeQRCode(e.target.checked)}
className="rounded border-slate-800 bg-[#0F1115] text-[#6366F1] focus:ring-0 w-3.5 h-3.5 cursor-pointer"
/>
<span>Render dynamic QR & sharing footer</span>
</label>
{/* Dynamic Status Watermarks Selection */}
<div className="pt-2 border-t border-slate-850/40 space-y-1.5">
<span className="block text-[9.5px] uppercase font-bold text-slate-400 tracking-wider font-mono">Document Print Watermark</span>
<div className="grid grid-cols-4 gap-1">
{(["none", "confidential", "draft", "custom"] as const).map((type) => (
<button
key={type}
type="button"
onClick={() => setPrintWatermarkType(type)}
className={`text-[8.5px] py-1 px-0.5 rounded border font-mono transition text-center cursor-pointer capitalize ${
printWatermarkType === type
? "bg-amber-500/15 border-amber-500/80 text-amber-300 font-bold"
: "bg-[#0F1115] border-slate-800 text-slate-400 hover:text-slate-200"
}`}
>
{type}
</button>
))}
</div>
{printWatermarkType === "custom" && (
<div className="mt-1 animate-fadeIn">
<input
type="text"
value={printWatermarkText}
onChange={(e) => setPrintWatermarkText(e.target.value.toUpperCase())}
maxLength={40}
placeholder="CUSTOM WATERMARK E.G. DRAFT V2..."
className="w-full text-[9px] px-2 py-1 bg-[#0F1115] border border-slate-850 rounded text-slate-200 font-mono focus:outline-none focus:border-amber-500/50 uppercase tracking-widest placeholder:text-slate-650"
/>
</div>
)}
</div>
</div>
</div>
{/* Synchronizer settings status line */}
<div className="flex items-center justify-between pt-2 border-t border-slate-800 text-[9px] font-mono select-none">
<span className="text-slate-500 font-sans">Preferences Sync</span>
{prefSyncStatus === "syncing" && (
<span className="text-amber-400 flex items-center gap-1 animate-pulse font-sans">
Synchronizing...
</span>
)}
{prefSyncStatus === "synced" && (
<span className="text-emerald-400 flex items-center gap-0.5 font-sans">
βœ“ Synced
</span>
)}
{prefSyncStatus === "error" && (
<span className="text-red-405 font-sans">⚠️ Error</span>
)}
{prefSyncStatus === "idle" && (
<span className="text-slate-400 hover:text-blue-405 cursor-pointer font-sans" onClick={syncPreferencesToServer}>
● Synced
</span>
)}
</div>
</div>
)}
<div className="hidden sm:block px-2.5 py-0.5 bg-amber-900/10 text-amber-500 border border-amber-500/20 rounded-full text-[10px] font-bold uppercase tracking-wider">
{user.tier.toUpperCase()}
</div>
<div className="hidden md:flex items-center space-x-2 text-xs text-slate-400">
<User className="h-3.5 w-3.5 text-slate-500" />
<span className="truncate max-w-[125px] font-mono text-xs">{user.email || "Guest"}</span>
</div>
</div>
</div>
</header>
);
}