fix(markdown-editor): align max width and formatting across read and edit views
This commit is contained in:
@@ -1,18 +1,30 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import ToolSidebar from './ToolSidebar';
|
||||
import NavigationConfirmModal from './NavigationConfirmModal';
|
||||
import useNavigationGuard from '../hooks/useNavigationGuard';
|
||||
import { Menu, X, ChevronDown, Terminal, Sparkles, Home } from 'lucide-react';
|
||||
import ThemeToggle from './ThemeToggle';
|
||||
import SEOHead from './SEOHead';
|
||||
import ConsentBanner from './ConsentBanner';
|
||||
import { NON_TOOLS, TOOLS, SITE_CONFIG, getCategoryConfig } from '../config/tools';
|
||||
import { useAnalytics } from '../hooks/useAnalytics';
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import ToolSidebar from "./ToolSidebar";
|
||||
import NavigationConfirmModal from "./NavigationConfirmModal";
|
||||
import useNavigationGuard from "../hooks/useNavigationGuard";
|
||||
import { Menu, X, ChevronDown, Terminal, Sparkles, Home } from "lucide-react";
|
||||
import ThemeToggle from "./ThemeToggle";
|
||||
import SEOHead from "./SEOHead";
|
||||
import ConsentBanner from "./ConsentBanner";
|
||||
import {
|
||||
NON_TOOLS,
|
||||
TOOLS,
|
||||
SITE_CONFIG,
|
||||
getCategoryConfig,
|
||||
} from "../config/tools";
|
||||
import { useAnalytics } from "../hooks/useAnalytics";
|
||||
|
||||
const Layout = ({ children }) => {
|
||||
const location = useLocation();
|
||||
const { showModal, pendingNavigation, handleConfirm, handleCancel, hasUnsavedData, navigateWithGuard } = useNavigationGuard();
|
||||
const {
|
||||
showModal,
|
||||
pendingNavigation,
|
||||
handleConfirm,
|
||||
handleCancel,
|
||||
hasUnsavedData,
|
||||
navigateWithGuard,
|
||||
} = useNavigationGuard();
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const dropdownRef = useRef(null);
|
||||
@@ -32,9 +44,9 @@ const Layout = ({ children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -45,10 +57,10 @@ const Layout = ({ children }) => {
|
||||
}, [location.pathname]);
|
||||
|
||||
// Check if we're on a tool page (not homepage)
|
||||
const isToolPage = location.pathname !== '/';
|
||||
const isToolPage = location.pathname !== "/";
|
||||
|
||||
// Check if we're on invoice preview page (no sidebar needed)
|
||||
const isInvoicePreviewPage = location.pathname === '/invoice-preview';
|
||||
const isInvoicePreviewPage = location.pathname === "/invoice-preview";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-indigo-900 flex flex-col">
|
||||
@@ -57,9 +69,18 @@ const Layout = ({ children }) => {
|
||||
|
||||
{/* Header */}
|
||||
<header className="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-slate-800/80 backdrop-blur-md shadow-lg border-b border-slate-200/50 dark:border-slate-700/50 flex-shrink-0">
|
||||
<div className={isToolPage ? "px-4 sm:px-6 lg:px-8" : "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"}>
|
||||
<div
|
||||
className={
|
||||
isToolPage
|
||||
? "px-4 sm:px-6 lg:px-8"
|
||||
: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
|
||||
}
|
||||
>
|
||||
<div className="flex justify-between items-center h-16">
|
||||
<button onClick={() => navigateWithGuard('/')} className="flex items-center group">
|
||||
<button
|
||||
onClick={() => navigateWithGuard("/")}
|
||||
className="flex items-center group"
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 rounded-lg blur opacity-20 group-hover:opacity-40 transition-opacity"></div>
|
||||
<div className="relative p-2">
|
||||
@@ -67,11 +88,11 @@ const Layout = ({ children }) => {
|
||||
src="/logo.svg"
|
||||
alt={SITE_CONFIG.title}
|
||||
className="h-8 w-auto"
|
||||
style={{ maxWidth: '150px' }}
|
||||
style={{ maxWidth: "150px" }}
|
||||
onError={(e) => {
|
||||
// Fallback to Terminal icon with text if logo fails to load
|
||||
e.target.style.display = 'none';
|
||||
e.target.nextSibling.style.display = 'flex';
|
||||
e.target.style.display = "none";
|
||||
e.target.nextSibling.style.display = "flex";
|
||||
}}
|
||||
/>
|
||||
<div className="hidden items-center space-x-3">
|
||||
@@ -91,12 +112,12 @@ const Layout = ({ children }) => {
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsDropdownOpen(false);
|
||||
navigateWithGuard('/');
|
||||
navigateWithGuard("/");
|
||||
}}
|
||||
className={`flex items-center space-x-2 px-4 py-2 rounded-xl text-sm font-medium transition-all duration-300 ${
|
||||
isActive('/')
|
||||
? 'bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-lg'
|
||||
: 'text-slate-600 dark:text-slate-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-white/50 dark:hover:bg-slate-700/50'
|
||||
isActive("/")
|
||||
? "bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-lg"
|
||||
: "text-slate-600 dark:text-slate-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-white/50 dark:hover:bg-slate-700/50"
|
||||
}`}
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
@@ -113,9 +134,11 @@ const Layout = ({ children }) => {
|
||||
>
|
||||
<Sparkles className="h-4 w-4" />
|
||||
<span>Tools</span>
|
||||
<ChevronDown className={`h-4 w-4 transition-transform duration-300 ${
|
||||
isDropdownOpen ? 'rotate-180' : ''
|
||||
}`} />
|
||||
<ChevronDown
|
||||
className={`h-4 w-4 transition-transform duration-300 ${
|
||||
isDropdownOpen ? "rotate-180" : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
@@ -125,7 +148,9 @@ const Layout = ({ children }) => {
|
||||
<div className="relative">
|
||||
{TOOLS.map((tool) => {
|
||||
const IconComponent = tool.icon;
|
||||
const categoryConfig = getCategoryConfig(tool.category);
|
||||
const categoryConfig = getCategoryConfig(
|
||||
tool.category,
|
||||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -136,16 +161,20 @@ const Layout = ({ children }) => {
|
||||
}}
|
||||
className={`group flex items-center space-x-4 px-4 py-3 text-sm hover:bg-white/50 dark:hover:bg-slate-700/50 transition-all duration-300 w-full text-left ${
|
||||
isActive(tool.path)
|
||||
? 'bg-gradient-to-r from-blue-50 to-purple-50 dark:from-slate-700 dark:to-slate-600 text-blue-700 dark:text-blue-300'
|
||||
: 'text-slate-700 dark:text-slate-300'
|
||||
? "bg-gradient-to-r from-blue-50 to-purple-50 dark:from-slate-700 dark:to-slate-600 text-blue-700 dark:text-blue-300"
|
||||
: "text-slate-700 dark:text-slate-300"
|
||||
}`}
|
||||
>
|
||||
<div className={`p-2 rounded-lg bg-gradient-to-br ${categoryConfig.color} shadow-sm group-hover:scale-110 transition-transform duration-300`}>
|
||||
<div
|
||||
className={`p-2 rounded-lg bg-gradient-to-br ${categoryConfig.color} shadow-sm group-hover:scale-110 transition-transform duration-300`}
|
||||
>
|
||||
<IconComponent className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{tool.name}</div>
|
||||
<div className="text-xs text-slate-600 dark:text-slate-600">{tool.description}</div>
|
||||
<div className="text-xs text-slate-600 dark:text-slate-600">
|
||||
{tool.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className="opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<ChevronDown className="h-4 w-4 -rotate-90 text-slate-600" />
|
||||
@@ -169,7 +198,11 @@ const Layout = ({ children }) => {
|
||||
aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"}
|
||||
aria-expanded={isMobileMenuOpen}
|
||||
>
|
||||
{isMobileMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
|
||||
{isMobileMenuOpen ? (
|
||||
<X className="h-6 w-6" />
|
||||
) : (
|
||||
<Menu className="h-6 w-6" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,12 +235,16 @@ const Layout = ({ children }) => {
|
||||
}}
|
||||
className={`flex items-center space-x-3 px-4 py-3 rounded-xl text-sm font-medium transition-all duration-300 w-full text-left ${
|
||||
isActive(tool.path)
|
||||
? 'bg-gradient-to-r from-indigo-500 to-purple-500 text-white shadow-lg'
|
||||
: 'text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/50 dark:hover:bg-slate-700/50'
|
||||
? "bg-gradient-to-r from-indigo-500 to-purple-500 text-white shadow-lg"
|
||||
: "text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/50 dark:hover:bg-slate-700/50"
|
||||
}`}
|
||||
>
|
||||
<div className={`p-2 rounded-lg ${isActive(tool.path) ? 'bg-white/20' : 'bg-gradient-to-br from-indigo-500 to-purple-500'} shadow-sm`}>
|
||||
<IconComponent className={`h-4 w-4 ${isActive(tool.path) ? 'text-white' : 'text-white'}`} />
|
||||
<div
|
||||
className={`p-2 rounded-lg ${isActive(tool.path) ? "bg-white/20" : "bg-gradient-to-br from-indigo-500 to-purple-500"} shadow-sm`}
|
||||
>
|
||||
<IconComponent
|
||||
className={`h-4 w-4 ${isActive(tool.path) ? "text-white" : "text-white"}`}
|
||||
/>
|
||||
</div>
|
||||
<span>{tool.name}</span>
|
||||
</button>
|
||||
@@ -217,7 +254,7 @@ const Layout = ({ children }) => {
|
||||
<div className="border-t border-slate-200/50 dark:border-slate-700/50 pt-4 mt-4">
|
||||
<div className="text-xs font-semibold text-slate-600 dark:text-slate-600 uppercase tracking-wider px-4 py-2 flex items-center gap-2">
|
||||
<Sparkles className="h-3 w-3" />
|
||||
{isToolPage ? 'Switch Tools' : 'Tools'}
|
||||
{isToolPage ? "Switch Tools" : "Tools"}
|
||||
</div>
|
||||
{TOOLS.map((tool) => {
|
||||
const IconComponent = tool.icon;
|
||||
@@ -232,16 +269,20 @@ const Layout = ({ children }) => {
|
||||
}}
|
||||
className={`flex items-center space-x-4 px-4 py-3 rounded-xl text-sm font-medium transition-all duration-300 w-full text-left ${
|
||||
isActive(tool.path)
|
||||
? 'bg-gradient-to-r from-blue-50 to-purple-50 dark:from-slate-700 dark:to-slate-600 text-blue-700 dark:text-blue-300'
|
||||
: 'text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/50 dark:hover:bg-slate-700/50'
|
||||
? "bg-gradient-to-r from-blue-50 to-purple-50 dark:from-slate-700 dark:to-slate-600 text-blue-700 dark:text-blue-300"
|
||||
: "text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/50 dark:hover:bg-slate-700/50"
|
||||
}`}
|
||||
>
|
||||
<div className={`p-2 rounded-lg bg-gradient-to-br ${categoryConfig.color} shadow-sm`}>
|
||||
<div
|
||||
className={`p-2 rounded-lg bg-gradient-to-br ${categoryConfig.color} shadow-sm`}
|
||||
>
|
||||
<IconComponent className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{tool.name}</div>
|
||||
<div className="text-xs text-slate-600 dark:text-slate-600">{tool.description}</div>
|
||||
<div className="text-xs text-slate-600 dark:text-slate-600">
|
||||
{tool.description}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -263,22 +304,18 @@ const Layout = ({ children }) => {
|
||||
<ToolSidebar navigateWithGuard={navigateWithGuard} />
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col pl-0 lg:pl-16 min-w-0">
|
||||
<div className="p-4 sm:p-6 w-full min-w-0 max-w-full overflow-x-hidden">
|
||||
<div className="p-4 sm:p-6 w-full min-w-0 max-w-full">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : isInvoicePreviewPage ? (
|
||||
<div className="flex-1 flex flex-col">
|
||||
<div className="flex-1">
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex-1">{children}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 flex flex-col">
|
||||
<div className="flex-1">
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex-1">{children}</div>
|
||||
{/* Global Footer for Homepage */}
|
||||
<footer className="bg-white/30 dark:bg-slate-800/30 backdrop-blur-sm border-t border-slate-200/30 dark:border-slate-700/30 mt-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
@@ -291,11 +328,11 @@ const Layout = ({ children }) => {
|
||||
src="/icon-192x192.png"
|
||||
alt={SITE_CONFIG.title}
|
||||
className="h-16 w-auto"
|
||||
style={{ maxWidth: '100px' }}
|
||||
style={{ maxWidth: "100px" }}
|
||||
onError={(e) => {
|
||||
// Fallback to Terminal icon with text if logo fails to load
|
||||
e.target.style.display = 'none';
|
||||
e.target.nextSibling.style.display = 'flex';
|
||||
e.target.style.display = "none";
|
||||
e.target.nextSibling.style.display = "flex";
|
||||
}}
|
||||
/>
|
||||
<div className="hidden items-center gap-3">
|
||||
@@ -334,21 +371,25 @@ const Layout = ({ children }) => {
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-xs">
|
||||
<button
|
||||
onClick={() => navigateWithGuard('/release-notes')}
|
||||
onClick={() => navigateWithGuard("/release-notes")}
|
||||
className="text-slate-600 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
|
||||
>
|
||||
Release Notes
|
||||
</button>
|
||||
<span className="text-slate-300 dark:text-slate-600">•</span>
|
||||
<span className="text-slate-300 dark:text-slate-600">
|
||||
•
|
||||
</span>
|
||||
<button
|
||||
onClick={() => navigateWithGuard('/privacy')}
|
||||
onClick={() => navigateWithGuard("/privacy")}
|
||||
className="text-slate-600 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
|
||||
>
|
||||
Privacy Policy
|
||||
</button>
|
||||
<span className="text-slate-300 dark:text-slate-600">•</span>
|
||||
<span className="text-slate-300 dark:text-slate-600">
|
||||
•
|
||||
</span>
|
||||
<button
|
||||
onClick={() => navigateWithGuard('/terms')}
|
||||
onClick={() => navigateWithGuard("/terms")}
|
||||
className="text-slate-600 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
|
||||
>
|
||||
Terms of Service
|
||||
@@ -373,11 +414,11 @@ const Layout = ({ children }) => {
|
||||
src="/icon-192x192.png"
|
||||
alt={SITE_CONFIG.title}
|
||||
className="h-16 w-auto"
|
||||
style={{ maxWidth: '100px' }}
|
||||
style={{ maxWidth: "100px" }}
|
||||
onError={(e) => {
|
||||
// Fallback to Terminal icon with text if logo fails to load
|
||||
e.target.style.display = 'none';
|
||||
e.target.nextSibling.style.display = 'flex';
|
||||
e.target.style.display = "none";
|
||||
e.target.nextSibling.style.display = "flex";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -390,21 +431,21 @@ const Layout = ({ children }) => {
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4 text-xs">
|
||||
<button
|
||||
onClick={() => navigateWithGuard('/release-notes')}
|
||||
onClick={() => navigateWithGuard("/release-notes")}
|
||||
className="text-slate-600 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
|
||||
>
|
||||
Release Notes
|
||||
</button>
|
||||
<span className="text-slate-300 dark:text-slate-600">•</span>
|
||||
<button
|
||||
onClick={() => navigateWithGuard('/privacy')}
|
||||
onClick={() => navigateWithGuard("/privacy")}
|
||||
className="text-slate-600 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
|
||||
>
|
||||
Privacy Policy
|
||||
</button>
|
||||
<span className="text-slate-300 dark:text-slate-600">•</span>
|
||||
<button
|
||||
onClick={() => navigateWithGuard('/terms')}
|
||||
onClick={() => navigateWithGuard("/terms")}
|
||||
className="text-slate-600 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
|
||||
>
|
||||
Terms of Service
|
||||
|
||||
Reference in New Issue
Block a user