All files / components Layout.tsx

100% Statements 10/10
90% Branches 9/10
100% Functions 4/4
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91                    1x 10x                             1x           10x 10x     10x 5x     10x                                                                                   1x 6x              
import React, { useState, useEffect } from 'react';
import { Helmet } from 'react-helmet';
import Header from './Header';
import Footer from './Footer';
import { ThemeProvider, useTheme } from '../context/ThemeContext';
import ThemeToggle from './ThemeToggle';
import AnimatedBackground from './AnimatedBackground';
import '../styles/global.css';
 
// Skip to content link for accessibility
const SkipToContent = () => (
  <a 
    href="#main-content" 
    className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 z-50 bg-neo-green-500 text-white px-4 py-2 rounded-md focus:outline-none"
  >
    Skip to content
  </a>
);
 
interface LayoutProps {
  children: React.ReactNode;
  title?: string;
  description?: string;
  showAnimatedBackground?: boolean;
}
 
const LayoutContent: React.FC<LayoutProps> = ({ 
  children, 
  title = 'Neo Rust SDK',
  description = 'A comprehensive Rust library for building applications on the Neo N3 blockchain ecosystem',
  showAnimatedBackground = false,
}) => {
  const { theme } = useTheme();
  const [mounted, setMounted] = useState(false);
  
  // After mounting, we can safely show the UI that depends on the theme
  useEffect(() => {
    setMounted(true);
  }, []);
 
  return (
    <>
      <Helmet>
        <html lang="en" className={theme} />
        <title>{title}</title>
        <meta name="description" content={description} />
        <meta property="og:title" content={title} />
        <meta property="og:description" content={description} />
        <meta property="og:type" content="website" />
        <meta property="og:image" content="/og-image.jpg" />
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:title" content={title} />
        <meta name="twitter:description" content={description} />
        {/* Theme color for browser UI */}
        <meta name="theme-color" content={theme === 'dark' ? '#0f172a' : '#ffffff'} />
      </Helmet>
      
      <div className={`relative ${mounted ? 'animate-fade-in' : 'opacity-0'}`}>
        <SkipToContent />
        
        {showAnimatedBackground && mounted && <AnimatedBackground />}
        
        <div className="flex flex-col min-h-screen">
          <Header />
          
          <main id="main-content" className="flex-grow pt-20">
            {children}
          </main>
          
          <Footer />
        </div>
        
        {/* Floating Theme Toggle */}
        <div className="fixed bottom-6 right-6 z-40">
          <ThemeToggle className="shadow-lg" />
        </div>
      </div>
    </>
  );
};
 
// Wrapper component that provides the ThemeContext
const Layout: React.FC<LayoutProps> = (props) => {
  return (
    <ThemeProvider>
      <LayoutContent {...props} />
    </ThemeProvider>
  );
};
 
export default Layout;