first commit
This commit is contained in:
commit
e131ae2efd
38 changed files with 18949 additions and 0 deletions
188
src/App.js
Normal file
188
src/App.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
// Import pages
|
||||
import HomePage from './pages/HomePage';
|
||||
import { Sun, Moon, Menu, X, ExternalLink } from 'lucide-react';
|
||||
|
||||
/*
|
||||
==============================================
|
||||
Main App Component
|
||||
==============================================
|
||||
*/
|
||||
const App = () => {
|
||||
/*
|
||||
==============================================
|
||||
State Management
|
||||
==============================================
|
||||
*/
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||
|
||||
/*
|
||||
==============================================
|
||||
Effects
|
||||
==============================================
|
||||
*/
|
||||
// Handle dark mode
|
||||
useEffect(() => {
|
||||
if (darkMode) {
|
||||
document.body.classList.add('dark-mode');
|
||||
} else {
|
||||
document.body.classList.remove('dark-mode');
|
||||
}
|
||||
}, [darkMode]);
|
||||
|
||||
// Add resize listener to handle responsive behavior
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
const mobile = window.innerWidth < 768;
|
||||
setIsMobile(mobile);
|
||||
|
||||
// Close mobile menu on desktop
|
||||
if (!mobile && mobileMenuOpen) {
|
||||
setMobileMenuOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [mobileMenuOpen]);
|
||||
|
||||
/*
|
||||
==============================================
|
||||
Event Handlers
|
||||
==============================================
|
||||
*/
|
||||
const toggleDarkMode = () => {
|
||||
setDarkMode(!darkMode);
|
||||
};
|
||||
|
||||
// Function to open social links
|
||||
const openSocialLink = (url) => {
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
// Social links data
|
||||
const socialLinks = [
|
||||
{ name: 'GitHub', url: 'https://github.com/charlottecroce' },
|
||||
{ name: 'Codeberg', url: 'https://codeberg.org/charlottecroce' },
|
||||
{ name: 'LinkedIn', url: 'https://linkedin.com/in/charlottecroce' },
|
||||
{ name: 'Mastodon', url: 'https://hachyderm.io/@charlotte200' }
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="app-container">
|
||||
{/*
|
||||
==============================================
|
||||
Navbar
|
||||
==============================================
|
||||
*/}
|
||||
<header className="navbar">
|
||||
<div className="navbar-container">
|
||||
<div className="navbar-logo">
|
||||
./charlotte.sh
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation Links */}
|
||||
<div className="navbar-links">
|
||||
{socialLinks.map((link) => (
|
||||
<button
|
||||
key={link.name}
|
||||
onClick={() => openSocialLink(link.url)}
|
||||
className="navbar-button"
|
||||
>
|
||||
{link.name} <ExternalLink size={16} className="ml-1" />
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
onClick={toggleDarkMode}
|
||||
aria-label={darkMode ? "Switch to light mode" : "Switch to dark mode"}
|
||||
className="icon-button"
|
||||
>
|
||||
{darkMode ? <Sun size={18} /> : <Moon size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
className="icon-button mobile-menu-toggle"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
aria-label={mobileMenuOpen ? "Close menu" : "Open menu"}
|
||||
style={{ display: isMobile ? 'flex' : 'none' }}
|
||||
>
|
||||
{mobileMenuOpen ? <X size={20} /> : <Menu size={20} />}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/*
|
||||
==============================================
|
||||
Mobile Menu
|
||||
==============================================
|
||||
*/}
|
||||
<div className={`mobile-menu ${mobileMenuOpen && isMobile ? 'open' : ''}`}>
|
||||
<div className="mobile-menu-container">
|
||||
{/* Social Links for Mobile */}
|
||||
{socialLinks.map((link) => (
|
||||
<button
|
||||
key={link.name}
|
||||
onClick={() => {
|
||||
openSocialLink(link.url);
|
||||
setMobileMenuOpen(false);
|
||||
}}
|
||||
className="mobile-button"
|
||||
>
|
||||
{link.name}
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
onClick={toggleDarkMode}
|
||||
className="mobile-button mobile-button-with-icon"
|
||||
>
|
||||
{darkMode ?
|
||||
<Sun size={18} className="icon-left" /> :
|
||||
<Moon size={18} className="icon-left" />
|
||||
}
|
||||
{darkMode ? 'Light Mode' : 'Dark Mode'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
==============================================
|
||||
Main Content
|
||||
==============================================
|
||||
*/}
|
||||
<main className="main-content">
|
||||
<HomePage />
|
||||
</main>
|
||||
|
||||
{/*
|
||||
==============================================
|
||||
Footer
|
||||
==============================================
|
||||
*/}
|
||||
<footer className="site-footer">
|
||||
<div className="social-footer-links" style={{ marginBottom: '16px' }}>
|
||||
{socialLinks.map((link, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{index > 0 && <span style={{ margin: '0 8px' }}>•</span>}
|
||||
<a
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="footer-link"
|
||||
>
|
||||
{link.name}
|
||||
</a>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
<p>©{new Date().getFullYear()} Charlotte Croce • Made with React JS & Codeberg Pages</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
Loading…
Add table
Add a link
Reference in a new issue