📱mobile responsiveness for navbar Menu
This commit is contained in:
@@ -5,11 +5,13 @@ import { useTranslations } from 'next-intl'
|
||||
import { Link } from '@/i18n/navigation'
|
||||
import { ThemeToggle } from '@/components/theme-toggle'
|
||||
import LanguageSwitcher from '@/components/layout/LanguageSwitcher'
|
||||
import { GlitchButton } from '@/components/effects/glitch-button'
|
||||
|
||||
export function Navbar() {
|
||||
const t = useTranslations('Navigation')
|
||||
const [isVisible, setIsVisible] = useState(true)
|
||||
const [lastScrollY, setLastScrollY] = useState(0)
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
@@ -19,6 +21,7 @@ export function Navbar() {
|
||||
setIsVisible(true)
|
||||
} else if (currentScrollY > lastScrollY) {
|
||||
setIsVisible(false)
|
||||
setIsMobileMenuOpen(false)
|
||||
} else {
|
||||
setIsVisible(true)
|
||||
}
|
||||
@@ -44,11 +47,12 @@ export function Navbar() {
|
||||
>
|
||||
< {t('home')}
|
||||
</Link>
|
||||
<span className="font-mono text-sm text-zinc-100 dark:text-zinc-300 uppercase tracking-wider">
|
||||
<span className="font-mono text-sm text-zinc-100 dark:text-zinc-300 uppercase tracking-wider hidden md:block">
|
||||
// <span style={{ color: 'var(--neon-pink)' }}>{t('blog')}</span> ARCHIVE
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
|
||||
<div className="hidden md:flex items-center gap-6">
|
||||
<Link
|
||||
href="/about"
|
||||
className="font-mono text-sm text-zinc-400 dark:text-zinc-500 uppercase tracking-wider hover:text-cyan-400 dark:hover:text-cyan-300 transition-colors cursor-pointer"
|
||||
@@ -64,7 +68,45 @@ export function Navbar() {
|
||||
<ThemeToggle />
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
|
||||
<div className="md:hidden flex items-center gap-4">
|
||||
<GlitchButton
|
||||
variant="subtle"
|
||||
glitchColor="cyan"
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
className="font-mono text-sm uppercase tracking-wider px-4 py-2 border-4 border-slate-700 bg-slate-800 dark:bg-zinc-900 text-zinc-100 dark:text-zinc-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-500"
|
||||
aria-label="Toggle mobile menu"
|
||||
aria-expanded={isMobileMenuOpen}
|
||||
>
|
||||
// {isMobileMenuOpen ? 'CLOSE' : 'MENU'}
|
||||
</GlitchButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isMobileMenuOpen && (
|
||||
<div className="md:hidden mt-4 pt-4 border-t-4 border-slate-700">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Link
|
||||
href="/about"
|
||||
className="font-mono text-sm text-zinc-400 dark:text-zinc-500 uppercase tracking-wider hover:text-cyan-400 dark:hover:text-cyan-300 transition-colors cursor-pointer px-4 py-2 border-2 border-slate-700"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
>
|
||||
[{t('about')}]
|
||||
</Link>
|
||||
<Link
|
||||
href="/blog"
|
||||
className="font-mono text-sm text-zinc-400 dark:text-zinc-500 uppercase tracking-wider hover:text-cyan-400 dark:hover:text-cyan-300 transition-colors cursor-pointer px-4 py-2 border-2 border-slate-700"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
>
|
||||
[{t('blog')}]
|
||||
</Link>
|
||||
<div className="flex items-center gap-4 px-4 py-2">
|
||||
<ThemeToggle />
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
|
||||
71
components/blog/navbar.tsx.bak
Normal file
71
components/blog/navbar.tsx.bak
Normal file
@@ -0,0 +1,71 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Link } from '@/i18n/navigation'
|
||||
import { ThemeToggle } from '@/components/theme-toggle'
|
||||
import LanguageSwitcher from '@/components/layout/LanguageSwitcher'
|
||||
|
||||
export function Navbar() {
|
||||
const t = useTranslations('Navigation')
|
||||
const [isVisible, setIsVisible] = useState(true)
|
||||
const [lastScrollY, setLastScrollY] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const currentScrollY = window.scrollY
|
||||
|
||||
if (currentScrollY < 10) {
|
||||
setIsVisible(true)
|
||||
} else if (currentScrollY > lastScrollY) {
|
||||
setIsVisible(false)
|
||||
} else {
|
||||
setIsVisible(true)
|
||||
}
|
||||
|
||||
setLastScrollY(currentScrollY)
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [lastScrollY])
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={`border-b-4 border-slate-700 bg-slate-900 dark:bg-zinc-950 sticky top-0 z-50 ${isVisible ? 'navbar-visible' : 'navbar-hidden'}`}
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-8">
|
||||
<Link
|
||||
href="/"
|
||||
className="font-mono text-sm uppercase tracking-wider transition-colors cursor-pointer"
|
||||
style={{ color: 'var(--neon-cyan)' }}
|
||||
>
|
||||
< {t('home')}
|
||||
</Link>
|
||||
<span className="font-mono text-sm text-zinc-100 dark:text-zinc-300 uppercase tracking-wider">
|
||||
// <span style={{ color: 'var(--neon-pink)' }}>{t('blog')}</span> ARCHIVE
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<Link
|
||||
href="/about"
|
||||
className="font-mono text-sm text-zinc-400 dark:text-zinc-500 uppercase tracking-wider hover:text-cyan-400 dark:hover:text-cyan-300 transition-colors cursor-pointer"
|
||||
>
|
||||
[{t('about')}]
|
||||
</Link>
|
||||
<Link
|
||||
href="/blog"
|
||||
className="font-mono text-sm text-zinc-400 dark:text-zinc-500 uppercase tracking-wider hover:text-cyan-400 dark:hover:text-cyan-300 transition-colors cursor-pointer"
|
||||
>
|
||||
[{t('blog')}]
|
||||
</Link>
|
||||
<ThemeToggle />
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
54
components/effects/glitch-button.tsx
Normal file
54
components/effects/glitch-button.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface GlitchButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode
|
||||
variant?: 'default' | 'subtle'
|
||||
glitchColor?: 'cyan' | 'pink' | 'purple' | 'magenta'
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function GlitchButton({
|
||||
children,
|
||||
variant = 'default',
|
||||
glitchColor = 'cyan',
|
||||
disabled = false,
|
||||
className,
|
||||
...props
|
||||
}: GlitchButtonProps) {
|
||||
const glitchClasses = !disabled
|
||||
? cn(
|
||||
'glitch-btn-cyber',
|
||||
variant === 'subtle' && 'glitch-btn-subtle',
|
||||
'relative'
|
||||
)
|
||||
: ''
|
||||
|
||||
const overlayColorClass = {
|
||||
cyan: '',
|
||||
pink: 'glitch-overlay-pink',
|
||||
purple: 'glitch-overlay-purple',
|
||||
magenta: 'glitch-overlay-magenta',
|
||||
}[glitchColor]
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(glitchClasses, className)}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
{!disabled && (
|
||||
<div
|
||||
className={cn('glitch-overlay', overlayColorClass)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
95
components/layout/hero-header.tsx
Normal file
95
components/layout/hero-header.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link } from '@/i18n/navigation'
|
||||
import Image from 'next/image'
|
||||
import { ThemeToggle } from '@/components/theme-toggle'
|
||||
import { GlitchButton } from '@/components/effects/glitch-button'
|
||||
|
||||
interface HeroHeaderProps {
|
||||
terminalVersion: string
|
||||
blogLabel: string
|
||||
aboutLabel: string
|
||||
}
|
||||
|
||||
export function HeroHeader({ terminalVersion, blogLabel, aboutLabel }: HeroHeaderProps) {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||
const [isMobile, setIsMobile] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const checkMobile = () => setIsMobile(window.innerWidth < 768)
|
||||
checkMobile()
|
||||
window.addEventListener('resize', checkMobile)
|
||||
return () => window.removeEventListener('resize', checkMobile)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="mb-8 border-b-2 border-slate-300 dark:border-slate-800 pb-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Image src="/logo.png" alt="Logo" width={32} height={32} className="opacity-80" />
|
||||
<span className="font-mono text-xs text-slate-500 uppercase tracking-widest">
|
||||
{terminalVersion}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{!isMobile && (
|
||||
<div className="flex gap-4 items-center">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400"
|
||||
>
|
||||
[{blogLabel}]
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400"
|
||||
>
|
||||
[{aboutLabel}]
|
||||
</Link>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isMobile && (
|
||||
<div>
|
||||
<GlitchButton
|
||||
variant="subtle"
|
||||
glitchColor="cyan"
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
className="font-mono text-xs uppercase tracking-wider px-3 py-2 border-2 border-slate-400 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300"
|
||||
aria-label="Toggle menu"
|
||||
aria-expanded={isMobileMenuOpen}
|
||||
>
|
||||
// {isMobileMenuOpen ? 'X' : 'MENU'}
|
||||
</GlitchButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isMobileMenuOpen && isMobile && (
|
||||
<div className="mt-4 pt-4 border-t-2 border-slate-300 dark:border-slate-800">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400 px-3 py-2 border-2 border-slate-300 dark:border-slate-700"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
>
|
||||
[{blogLabel}]
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400 px-3 py-2 border-2 border-slate-300 dark:border-slate-700"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
>
|
||||
[{aboutLabel}]
|
||||
</Link>
|
||||
<div className="px-3 py-2">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user