deniz bektaş
This commit is contained in:
285
components/Navigation.tsx
Normal file
285
components/Navigation.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useTheme } from './ThemeProvider';
|
||||
|
||||
const navGroups = [
|
||||
{
|
||||
label: 'Ana',
|
||||
items: [
|
||||
{ href: '/', label: 'Anasayfa', icon: '~' },
|
||||
{ href: '/merhaba', label: 'Merhaba', icon: '>' },
|
||||
{ href: '/hakkimda', label: 'Hakkımda', icon: '@' },
|
||||
{ href: '/su-anda', label: 'Şu Anda', icon: '◉' },
|
||||
{ href: '/misyon', label: 'Misyon', icon: '⚑' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'İçerik',
|
||||
items: [
|
||||
{ href: '/blog', label: 'Personal Blog', icon: '✍' },
|
||||
{ href: '/notebook', label: 'Notebook', icon: '◎' },
|
||||
{ href: '/infosec', label: 'Infosec Posts', icon: '⚔' },
|
||||
{ href: '/seyir-defteri', label: 'Seyir Defteri', icon: '⚓' },
|
||||
{ href: '/projeler', label: 'Projeler', icon: '◈' },
|
||||
{ href: '/podcast', label: 'Podcast', icon: '◉' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Koleksiyon',
|
||||
items: [
|
||||
{ href: '/faydali-linkler', label: 'Faydalı Linkler', icon: '⊞' },
|
||||
{ href: '/roadmap', label: 'Roadmap', icon: '◆' },
|
||||
{ href: '/takim-cantam', label: 'Takım Çantam', icon: '⊡' },
|
||||
{ href: '/hobilerim', label: 'Hobilerim', icon: '♦' },
|
||||
{ href: '/fikirlerim', label: 'Fikirlerim', icon: '◇' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Bilgi',
|
||||
items: [
|
||||
{ href: '/iletisim', label: 'İletişim', icon: '✉' },
|
||||
{ href: '/ozgecmis', label: 'Özgeçmiş', icon: '▤' },
|
||||
{ href: '/aktivite', label: 'Aktivite', icon: '▶' },
|
||||
{ href: '/rss-beslemeleri', label: 'RSS Beslemeleri', icon: '◈' },
|
||||
{ href: '/statboard', label: 'Statboard', icon: '▦' },
|
||||
{ href: '/altyapi', label: 'Altyapı', icon: '⊟' },
|
||||
{ href: '/tesekkurler', label: 'Teşekkürler', icon: '♡' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Meta',
|
||||
items: [
|
||||
{ href: '/gizlilik', label: 'Gizlilik', icon: '⊘' },
|
||||
{ href: '/kullanim-kosullari', label: 'Kullanım Koşulları', icon: '⊘' },
|
||||
{ href: '/humans.txt', label: 'humans.txt', icon: '▸', external: true },
|
||||
{ href: '/security.txt', label: 'security.txt', icon: '▸', external: true },
|
||||
{ href: '/public.pgp', label: 'public.pgp', icon: '▸', external: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Harici',
|
||||
items: [
|
||||
{ href: '/mastodon', label: 'Mastodon', icon: '⊕', external: true },
|
||||
{ href: 'mailto:info@denizbektas.com.tr', label: 'E-Mail', icon: '✉', external: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function Navigation() {
|
||||
const pathname = usePathname();
|
||||
const { theme, toggle } = useTheme();
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
|
||||
const isActive = (href: string) => {
|
||||
if (href === '/') return pathname === '/';
|
||||
return pathname.startsWith(href);
|
||||
};
|
||||
|
||||
const sidebarContent = (
|
||||
<nav style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
|
||||
{navGroups.map((group) => (
|
||||
<div key={group.label}>
|
||||
<div className="section-header">{group.label}</div>
|
||||
<ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '1px' }}>
|
||||
{group.items.map((item) => (
|
||||
<li key={item.href}>
|
||||
{item.external && item.href.startsWith('mailto:') ? (
|
||||
<a
|
||||
href={item.href}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '0.3rem 0.5rem',
|
||||
borderRadius: '4px',
|
||||
fontSize: '0.93rem',
|
||||
color: 'var(--text-muted)',
|
||||
transition: 'all 0.15s',
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'var(--accent)', width: '1.1rem', flexShrink: 0, fontSize: '0.82rem' }}>{item.icon}</span>
|
||||
{item.label}
|
||||
</a>
|
||||
) : item.external && !item.href.startsWith('/') ? (
|
||||
<a
|
||||
href={item.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '0.3rem 0.5rem',
|
||||
borderRadius: '4px',
|
||||
fontSize: '0.93rem',
|
||||
color: 'var(--text-muted)',
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'var(--accent)', width: '1.1rem', flexShrink: 0, fontSize: '0.82rem' }}>{item.icon}</span>
|
||||
{item.label}
|
||||
<span style={{ marginLeft: 'auto', fontSize: '0.7rem', opacity: 0.5 }}>↗</span>
|
||||
</a>
|
||||
) : (
|
||||
<Link
|
||||
href={item.href}
|
||||
target={item.external ? '_blank' : undefined}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '0.35rem 0.5rem',
|
||||
borderRadius: '4px',
|
||||
fontSize: '0.93rem',
|
||||
color: isActive(item.href) ? 'var(--accent)' : 'var(--text-muted)',
|
||||
background: isActive(item.href) ? 'var(--tag-bg)' : 'transparent',
|
||||
fontWeight: isActive(item.href) ? '600' : '400',
|
||||
transition: 'all 0.15s',
|
||||
opacity: 1,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'var(--accent)', width: '1.1rem', flexShrink: 0, fontSize: '0.82rem' }}>{item.icon}</span>
|
||||
{item.label}
|
||||
{item.external && <span style={{ marginLeft: 'auto', fontSize: '0.7rem', opacity: 0.5 }}>↗</span>}
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Desktop Sidebar */}
|
||||
<aside
|
||||
className="desktop-sidebar"
|
||||
style={{
|
||||
width: '272px',
|
||||
flexShrink: 0,
|
||||
background: 'var(--sidebar-bg)',
|
||||
borderRight: '1px solid var(--border)',
|
||||
height: '100vh',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{/* Logo */}
|
||||
<div style={{ padding: '1.25rem 1rem', borderBottom: '1px solid var(--border)' }}>
|
||||
<Link href="/" style={{ display: 'block', color: 'var(--accent)', fontWeight: '800', fontSize: '1.1rem', letterSpacing: '-0.02em' }}>
|
||||
denizbektas<span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>.com.tr</span>
|
||||
</Link>
|
||||
<div style={{ fontSize: '0.78rem', color: 'var(--text-muted)', marginTop: '0.25rem' }}>siber güvenlik uzmanı</div>
|
||||
</div>
|
||||
|
||||
{/* Theme Toggle */}
|
||||
<div style={{ padding: '0.75rem 1rem', borderBottom: '1px solid var(--border)' }}>
|
||||
<button
|
||||
onClick={toggle}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
fontSize: '0.88rem',
|
||||
color: 'var(--text-muted)',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0.35rem 0.5rem',
|
||||
borderRadius: '4px',
|
||||
width: '100%',
|
||||
fontFamily: 'inherit',
|
||||
transition: 'color 0.15s',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '0.9rem' }}>{theme === 'dark' ? '☀' : '◑'}</span>
|
||||
{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Nav */}
|
||||
<div style={{ padding: '1rem', flex: 1 }}>
|
||||
{sidebarContent}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div style={{ padding: '0.75rem 1rem', borderTop: '1px solid var(--border)', fontSize: '0.6rem', color: 'var(--text-muted)' }}>
|
||||
<div>© 2026 denizbektas</div>
|
||||
<div style={{ marginTop: '0.2rem', color: 'var(--accent)', opacity: 0.6 }}>
|
||||
<span className="blink">█</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Mobile Header */}
|
||||
<header
|
||||
className="mobile-header"
|
||||
style={{
|
||||
display: 'none',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '52px',
|
||||
background: 'var(--header-bg)',
|
||||
backdropFilter: 'blur(8px)',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
zIndex: 100,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0 1rem',
|
||||
}}
|
||||
>
|
||||
<Link href="/" style={{ color: 'var(--accent)', fontWeight: '800', fontSize: '0.95rem' }}>
|
||||
denizbektas<span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>.dev</span>
|
||||
</Link>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||||
<button onClick={toggle} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: '1rem', color: 'var(--text-muted)' }}>
|
||||
{theme === 'dark' ? '☀' : '◑'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMobileOpen(!mobileOpen)}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text)', fontSize: '1.2rem', fontFamily: 'inherit' }}
|
||||
>
|
||||
{mobileOpen ? '✕' : '☰'}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Mobile Nav Overlay */}
|
||||
{mobileOpen && (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: '52px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'var(--sidebar-bg)',
|
||||
zIndex: 99,
|
||||
overflowY: 'auto',
|
||||
padding: '1rem',
|
||||
borderTop: '1px solid var(--border)',
|
||||
}}
|
||||
>
|
||||
{sidebarContent}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<style>{`
|
||||
@media (max-width: 768px) {
|
||||
.desktop-sidebar { display: none !important; }
|
||||
.mobile-header { display: flex !important; }
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
);
|
||||
}
|
||||
36
components/ThemeProvider.tsx
Normal file
36
components/ThemeProvider.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
type Theme = 'dark' | 'light';
|
||||
|
||||
const ThemeContext = createContext<{
|
||||
theme: Theme;
|
||||
toggle: () => void;
|
||||
}>({ theme: 'dark', toggle: () => {} });
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [theme, setTheme] = useState<Theme>('dark');
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem('theme') as Theme | null;
|
||||
const initial = stored || 'dark';
|
||||
setTheme(initial);
|
||||
document.documentElement.setAttribute('data-theme', initial);
|
||||
}, []);
|
||||
|
||||
const toggle = () => {
|
||||
const next = theme === 'dark' ? 'light' : 'dark';
|
||||
setTheme(next);
|
||||
localStorage.setItem('theme', next);
|
||||
document.documentElement.setAttribute('data-theme', next);
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, toggle }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
Reference in New Issue
Block a user